Skip to main content
The Sava SDK provides comprehensive cryptographic utilities for Ed25519 key operations, signature generation and verification, and various hashing functions used in Solana development.

Ed25519 Key Generation

Solana uses Ed25519 for all cryptographic signing operations. The Sava SDK provides utilities for generating and managing Ed25519 keypairs.

Generating Keys

import software.sava.core.accounts.Signer;
import software.sava.core.crypto.ed25519.Ed25519Util;

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

// Generate complete keypair (32-byte private + 32-byte public)
byte[] keyPair = Signer.generatePrivateKeyPairBytes();

// Generate public key from private key
byte[] publicKey = new byte[32];
Ed25519Util.generatePublicKey(privateKey, publicKey);

// With custom offsets
Ed25519Util.generatePublicKey(
    privateKey,
    privateKeyOffset,
    publicKey,
    publicKeyOffset
);

Key Constants

int KEY_LENGTH = Signer.KEY_LENGTH;  // 32 bytes
int PUBLIC_KEY_LENGTH = PublicKey.PUBLIC_KEY_LENGTH;  // 32 bytes
int SIGNATURE_LENGTH = Transaction.SIGNATURE_LENGTH;   // 64 bytes

Creating Signers

The Signer interface provides secure key management with automatic validation:
// Create signer from private key
Signer signer = Signer.createFromPrivateKey(privateKeyBytes);

// Create from keypair bytes (64 bytes: private + public)
Signer signer = Signer.createFromKeyPair(keyPairBytes);

// Create from separate keys
Signer signer = Signer.createFromKeyPair(
    publicKeyBytes,
    privateKeyBytes
);

// Access keys
PublicKey publicKey = signer.publicKey();
PrivateKey privateKey = signer.privateKey();
The SDK automatically validates that the public key matches the private key when creating signers. Invalid keypairs will throw an IllegalStateException.

Signing Messages

The Signer interface provides multiple methods for signing data:

Basic Signing

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

// Sign portion of message
byte[] signature = signer.sign(message, offset, length);

// Sign in-place (returns next offset)
int nextPos = signer.sign(
    message,
    msgOffset,
    msgLength,
    outputPosition
);

Transaction Signing

import software.sava.core.tx.Transaction;

// Sign transaction (modifies transaction in place)
Transaction.sign(signer, txData);

// Sign with multiple signers
Transaction.sign(signers, txData, msgOffset, msgLength, sigOffset);

// Sign transaction object
tx.sign(signer);
tx.sign(List.of(signer1, signer2, signer3));

Signature Verification

The SDK provides multiple ways to verify Ed25519 signatures:

Using PublicKey

import software.sava.core.accounts.PublicKey;

// Verify signature
boolean isValid = publicKey.verifySignature(message, signature);

// Verify with offset and length
boolean isValid = publicKey.verifySignature(
    message,
    msgOffset,
    msgLength,
    signature
);

// Verify string message
boolean isValid = publicKey.verifySignature(
    "Hello Solana",
    signature
);

Static Verification

// Verify with byte arrays
boolean isValid = PublicKey.verifySignature(
    publicKeyBytes,
    publicKeyOffset,
    message,
    msgOffset,
    msgLength,
    signature
);

// Verify Java PublicKey
boolean isValid = PublicKey.verifySignature(
    javaPublicKey,
    message,
    signature
);

Example: Complete Signing Flow

// Generate keypair
byte[] privateKey = Signer.generatePrivateKeyBytes();
Signer signer = Signer.createFromPrivateKey(privateKey);

// Sign message
String message = "Transaction data";
byte[] signature = signer.sign(message.getBytes());

// Verify signature
boolean valid = signer.publicKey().verifySignature(message, signature);
assert valid : "Signature verification failed";

Hash Functions

The Sava SDK provides secure hashing utilities for various Solana operations.

SHA-256

import software.sava.core.crypto.Hash;
import java.security.MessageDigest;

// One-shot hash
byte[] hash = Hash.sha256(input);

// Reusable digest
MessageDigest digest = Hash.sha256Digest();
digest.update(data1);
digest.update(data2);
byte[] hash = digest.digest();

// Double SHA-256 (Bitcoin-style)
byte[] doubleHash = Hash.sha256Twice(input);
byte[] doubleHash = Hash.sha256Twice(input, offset, length);

SHA-512

// Get SHA-512 digest instance
MessageDigest digest = Hash.sha512Digest();
digest.update(privateKey);
byte[] hash = digest.digest();

RIPEMD-160 (Hash-160)

// SHA-256 followed by RIPEMD-160
byte[] hash160 = Hash.h160(input);

Usage in PDA Derivation

import software.sava.core.accounts.PublicKey;
import software.sava.core.crypto.Hash;
import java.util.List;

// PDA derivation uses SHA-256
List<byte[]> seeds = List.of(
    "metadata".getBytes(),
    publicKey.toByteArray()
);

ProgramDerivedAddress pda = PublicKey.findProgramAddress(
    seeds,
    programId
);
// Internally uses Hash.sha256Digest() to hash seeds + bump + program ID

Curve Operations

The SDK includes utilities for checking if points are on the Ed25519 curve:

Off-Curve Validation

import software.sava.core.crypto.ed25519.Ed25519Util;

// Check if public key is off-curve (used for PDAs)
boolean isOffCurve = Ed25519Util.isNotOnCurve(publicKeyBytes);

// PDAs must be off-curve to be valid
if (isOffCurve) {
    // Valid PDA
} else {
    // Invalid PDA, try next bump seed
}

PDA Search Algorithm

// Find PDA by searching for off-curve address
public static ProgramDerivedAddress findPDA(
    List<byte[]> seeds,
    PublicKey programId
) {
    var sha256 = Hash.sha256Digest();
    
    // Try bump seeds from 255 down to 0
    for (int nonce = 255; nonce >= 0; --nonce) {
        buffer[nonceOffset] = (byte) nonce;
        byte[] hash = sha256.digest(buffer);
        
        // Check if off-curve
        if (Ed25519Util.isNotOnCurve(hash)) {
            return ProgramDerivedAddress.createPDA(
                seeds,
                PublicKey.createPubKey(hash),
                nonce
            );
        }
    }
    
    throw new RuntimeException("Unable to find PDA");
}

HMAC Operations

HMAC (Hash-based Message Authentication Code) is used for key derivation:
import software.sava.core.crypto.Hmac;
import javax.crypto.Mac;

// HMAC-SHA512 (used in BIP32/44 key derivation)
Mac hmac = Hmac.hmacSha512(key);
hmac.update(data);
byte[] result = hmac.doFinal();

Key Conversion

Convert between different key representations:

To Java PublicKey

import java.security.PublicKey as JavaPublicKey;

// Convert Sava PublicKey to Java PublicKey
JavaPublicKey javaKey = publicKey.toJavaPublicKey();

// Convert from bytes
JavaPublicKey javaKey = PublicKey.toJavaPublicKey(publicKeyBytes);

// With offset
JavaPublicKey javaKey = PublicKey.toJavaPublicKey(
    buffer,
    offset,
    length
);

Secure Random Generation

The SDK uses SecureRandom for all key generation:
import software.sava.core.crypto.SunCrypto;
import java.security.SecureRandom;

// Access the secure random instance
SecureRandom random = SunCrypto.SECURE_RANDOM;

// Generate random bytes
byte[] randomBytes = new byte[32];
random.nextBytes(randomBytes);
The SDK uses the Sun security provider for all cryptographic operations, ensuring compatibility and performance.

Key Validation

Validate keypairs before use:
// Validate private/public key pair
Signer.validateKeyPair(privateKey, expectedPublicKey);
// Throws IllegalStateException if keys don't match

// Validate complete keypair
Signer.validateKeyPair(keyPairBytes);
// Validates both the keypair structure and cryptographic validity

Best Practices

  1. Use SecureRandom: Always use the SDK’s secure random for key generation
  2. Validate imported keys: Call validateKeyPair on keys from external sources
  3. Protect private keys: Never log or expose private key material
  4. Cache digest instances: Reuse MessageDigest for repeated hashing
  5. Verify signatures: Always verify signatures before processing
  6. Use constant-time comparisons: The SDK’s verification methods are timing-safe

Security Considerations

  • Never reuse nonces or random values across different operations
  • Always validate external public keys before use
  • Use dedicated signers for each transaction to avoid key exposure
  • Implement proper key rotation policies in production systems

Complete Example

import software.sava.core.accounts.Signer;
import software.sava.core.accounts.PublicKey;
import software.sava.core.crypto.Hash;

// Generate new keypair
byte[] privateKey = Signer.generatePrivateKeyBytes();
Signer signer = Signer.createFromPrivateKey(privateKey);

// Sign data
byte[] data = "Important message".getBytes();
byte[] signature = signer.sign(data);

// Hash the data
byte[] dataHash = Hash.sha256(data);

// Verify signature
boolean valid = signer.publicKey().verifySignature(data, signature);
assert valid : "Signature verification failed";

// Convert to Java PublicKey for interop
java.security.PublicKey javaKey = signer.publicKey().toJavaPublicKey();

// Verify using Java API
boolean javaValid = PublicKey.verifySignature(
    javaKey,
    data,
    signature
);
assert javaValid : "Java verification failed";

Build docs developers (and LLMs) love