Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Custom Signers

The Interface

To implement a custom signer, you must implement the Signer interface.

public interface Signer {
    Signature signTransaction(UnsignedTransaction tx, long chainId);
    Signature signMessage(byte[] message);
    Address address();
}

Example: External Service Integration

Here is how you might implement a signer that delegates to an external API (e.g., Privy or a backend KMS).

import sh.brane.core.crypto.Signer;
import sh.brane.core.crypto.Signature;
import sh.brane.core.types.Address;
import sh.brane.core.tx.UnsignedTransaction;
import sh.brane.core.crypto.Keccak256;
 
// Dummy interface for the example
interface ExternalService {
    Signature sign(String keyId, byte[] hash);
}
 
public class RemoteSigner implements Signer {
    private final String keyId;
    private final Address address;
    private final ExternalService service;
 
    public RemoteSigner(String keyId, Address address, ExternalService service) {
        this.keyId = keyId;
        this.address = address;
        this.service = service;
    }
 
    @Override
    public Address address() {
        return address;
    }
 
    @Override
    public Signature signTransaction(UnsignedTransaction tx, long chainId) {
        // 1. Get the transaction hash (preimage)
        byte[] preimage = tx.encodeForSigning(chainId);
        byte[] hash = Keccak256.hash(preimage);
 
        // 2. Request signature from external service
        // (This is where you call Privy, AWS KMS, etc.)
        return service.sign(this.keyId, hash);
    }
 
    @Override
    public Signature signMessage(byte[] message) {
        // 1. Create the EIP-191 prefixed message
        byte[] prefix = ("\u0019Ethereum Signed Message:\n" + message.length)
                .getBytes(java.nio.charset.StandardCharsets.UTF_8);
        byte[] prefixedMessage = java.nio.ByteBuffer.allocate(prefix.length + message.length)
                .put(prefix)
                .put(message)
                .array();
 
        // 2. Hash the prefixed message
        byte[] hash = Keccak256.hash(prefixedMessage);
 
        // 3. Request signature for the hash
        Signature sig = service.sign(this.keyId, hash);
 
        // Adjust v to be 27 or 28 for EIP-191 compatibility
        return new Signature(sig.r(), sig.s(), sig.v() + 27);
    }
}

Usage with Brane.Signer

Once you have your custom signer, pass it to Brane.connect() to create a Brane.Signer client.

import sh.brane.rpc.Brane;
 
// Assume service is defined
ExternalService service = ...;
 
Signer mySigner = new RemoteSigner(
    "key-123",
    new Address("0x..."),
    service
);
 
// Create a Brane.Signer client with your custom signer
Brane.Signer client = Brane.connect("https://rpc.sepolia.org", mySigner);
 
// Now you can send transactions
TransactionReceipt receipt = client.sendTransactionAndWait(request, 60_000, 1_000);