Signers
The Signer Interface
In Brane, all signing operations are abstracted through the Signer interface. This provides a unified way to sign both transactions and raw messages, whether the key is held locally (e.g., a private key) or remotely (e.g., KMS, HSM, or MPC).
public interface Signer {
/**
* Signs a transaction.
*/
Signature signTransaction(UnsignedTransaction tx, long chainId);
/**
* Signs a raw message (EIP-191).
*/
Signature signMessage(byte[] message);
/**
* Returns the address associated with this signer.
*/
Address address();
}PrivateKeySigner
The most common implementation is PrivateKeySigner, which uses a local private key.
import sh.brane.core.crypto.PrivateKeySigner;
// Initialize with a hex private key
var signer = new PrivateKeySigner("0x...");
// Get the address
System.out.println("Address: " + signer.address());HD Wallets (MnemonicWallet)
For managing multiple accounts from a single mnemonic phrase, use MnemonicWallet:
import sh.brane.core.crypto.hd.MnemonicWallet;
// Generate a new 12-word phrase
MnemonicWallet wallet = MnemonicWallet.generatePhrase();
String phrase = wallet.phrase(); // Save this securely!
// Or restore from existing phrase
MnemonicWallet restored = MnemonicWallet.fromPhrase(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
);
// Derive signers for different addresses
Signer account0 = wallet.derive(0); // m/44'/60'/0'/0/0
Signer account1 = wallet.derive(1); // m/44'/60'/0'/0/1HD wallets follow BIP-39 for mnemonic phrases and BIP-44 for Ethereum key derivation. For comprehensive documentation including passphrases, custom derivation paths, and security best practices, see HD Wallets.
PrivateKey
The underlying PrivateKey class handles secp256k1 key operations directly.
Creating Keys
import sh.brane.core.crypto.PrivateKey;
// From hex string (most common)
PrivateKey key = PrivateKey.fromHex("0x...");
// From raw bytes
byte[] keyBytes = loadKeyFromSecureStorage();
PrivateKey key = PrivateKey.fromBytes(keyBytes);
// Note: keyBytes is zeroed after this call for securityByte Array Ownership
PrivateKey.fromBytes() takes ownership of the input byte array and zeros it after extracting the key material. This minimizes exposure of sensitive data in memory.
byte[] keyBytes = loadKeyFromSecureStorage();
PrivateKey key = PrivateKey.fromBytes(keyBytes);
// keyBytes is now all zeros!
// If you need to retain the original bytes, clone first:
byte[] keyBytes = loadKeyFromSecureStorage();
PrivateKey key = PrivateKey.fromBytes(keyBytes.clone());
// keyBytes still contains original dataKey Destruction
PrivateKey implements Destroyable for explicit cleanup of sensitive material:
PrivateKey key = PrivateKey.fromHex("0x...");
try {
Address address = key.toAddress();
Signature sig = key.sign(messageHash);
} finally {
key.destroy(); // Clear internal references
}
// After destroy(), any operation throws IllegalStateException
key.sign(hash); // throws IllegalStateExceptionAll PrivateKey operations are thread-safe. Concurrent signing and destruction are handled correctly.
Signing Transactions
When using Brane.connect(url, signer) to create a Brane.Signer client, the signer is handled automatically. However, you can also sign directly:
import sh.brane.core.tx.LegacyTransaction;
import sh.brane.core.types.Wei;
// Create an unsigned transaction
var tx = new LegacyTransaction(
0L, // nonce
Wei.gwei(20), // gasPrice
21000L, // gasLimit
recipientAddress, // to
Wei.fromEther(new java.math.BigDecimal("0.01")), // value
new byte[0] // data
);
Signature sig = signer.signTransaction(tx, 1L); // Chain ID 1 (Mainnet)Signing Messages
You can sign arbitrary messages, which is useful for authentication (e.g., "Sign in with Ethereum") or off-chain approvals.
import java.nio.charset.StandardCharsets;
String message = "Hello Brane!";
Signature sig = signer.signMessage(message.getBytes(StandardCharsets.UTF_8));
System.out.println("Signature: " + sig);EIP-712 Typed Data Signing
The Signer interface includes support for EIP-712 structured data signing, which enables domain-separated signatures for protocols like ERC-2612 permits and off-chain order signing.
// Sign with Map-based API (dynamic typing)
Signature signTypedData(
Eip712Domain domain,
String primaryType,
Map<String, List<TypedDataField>> types,
Map<String, Object> message);
// Sign with TypedData API (type-safe)
Signature signTypedData(TypedData<?> typedData);Example usage:
import sh.brane.core.crypto.eip712.Eip712Domain;
import sh.brane.core.crypto.eip712.TypedData;
// Create domain and typed data
Eip712Domain domain = Eip712Domain.builder()
.name("MyDapp")
.version("1")
.chainId(1L)
.verifyingContract(contractAddress)
.build();
TypedData<Permit> typedData = TypedData.create(domain, Permit.DEFINITION, permit);
// Sign using the signer
Signature sig = signer.signTypedData(typedData);For comprehensive documentation on EIP-712 typed data signing, including type definitions, nested types, and JSON parsing for wallet integration, see EIP-712 Typed Data.