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
- Use SecureRandom: Always use the SDK’s secure random for key generation
- Validate imported keys: Call
validateKeyPair on keys from external sources
- Protect private keys: Never log or expose private key material
- Cache digest instances: Reuse
MessageDigest for repeated hashing
- Verify signatures: Always verify signatures before processing
- 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";