Server-Side Wallet Signing: 5 Backend Transaction Signing Architectures (KMS, MPC, Session Keys, TEEs)

A practical guide to the five server-side signing architectures, from raw keys to MPC to TEE, with real code and honest tradeoffs.

Server-Side Wallet Signing: 5 Backend Transaction Signing Architectures (KMS, MPC, Session Keys, TEEs)
Most embedded wallet tutorials live on the frontend: social login, passkeys, one-click transactions. Production systems look different. The highest volume onchain actions usually happen without a user clicking approve.

Why Does Your Backend Need to Sign Transactions?

Every time your application distributes tokens, pays employees in stablecoins, rebalances a DeFi position, mints game items, relays gasless transactions, or submits oracle data, a server must sign a transaction without a human in the loop.

Four production use cases require server-side signing:

  1. Airdrops and rewards. Distribute tokens to thousands of addresses based on eligibility criteria. Requires high throughput and correct nonce sequencing.
  2. Crypto payroll. Recurring stablecoin transfers on a schedule. Requires spending limits and audit trails for tax compliance. One of our favorite examples of this is our customer, Qash.
  3. DeFi automation. Rebalancing, yield harvesting, compounding. Requires multi-step transaction sequences (approve, swap, deposit) with simulation before execution. See KeeperHub.
  4. Game economies. Server-side NFT minting and reward distribution. Players expect sub-second feedback, meaning the signing wallet needs low latency and high concurrency.

The common thread: all four patterns need a private key available to server code, protected from compromise, with policy controls to limit what it can do.

What Are the Five Server-Side Signing Architectures?

Each architecture trades off security, cost, complexity, and throughput. The spectrum runs from simplest (and most dangerous) to most sophisticated.

1) Raw Private Key in an Environment Variable

// This is an anti-pattern. Do not use in production.
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
const tx = await wallet.sendTransaction({ to, value, data });

The private key sits in an .env file, loaded at startup, available in process memory. Anyone with access to the environment (DevOps engineers, CI/CD logs, container dashboards, crash dumps) can extract the key and drain all funds instantly.

Tradeoff: No access control granularity (the key signs anything), no audit trail, no rotation without changing the wallet address, and secrets sprawl across Docker images, Kubernetes secrets (base64, not encrypted), and developer laptops.

In 2024, infrastructure attacks caused by private key compromises accounted for nearly 70% of all stolen cryptocurrency funds. Many traces back to leaked environment variables and compromised CI/CD pipelines.

2) Cloud KMS-Backed Signing

Instead of storing a private key in your application, create an asymmetric key in AWS KMS, GCP Cloud KMS, or Azure Key Vault. The private key material never leaves the KMS hardware boundary. Your application sends a hash, receives a signature, and assembles the signed transaction.

FeatureAWS KMSGCP Cloud KMSAzure Key Vault
secp256k1 support`ECC_SECG_P256K1``EC_SIGN_SECP256K1_SHA256``P-256K`
HSM backingFIPS 140-2 Level 2 (default)FIPS 140-2 Level 3 (HSM tier)FIPS 140-2 Level 2/3
Signing latency50-200ms50-200ms50-200ms
Cost per 10K signs~$0.03~$0.03~$0.03
Audit loggingCloudTrailCloud Audit LogsAzure Monitor


Tradeoff: KMS returns signatures in ASN.1 DER format, not the (r, s, v) tuple Ethereum expects. You must decode the DER encoding, normalize the s value (EIP-2), and recover the v parameter. This conversion is non-trivial and a common source of bugs.

KMS also adds a practical constraint: it gives you strong key isolation, but it does not automatically give you wallet-native policy controls. You still need allowlists, rate limits, and guardrails around what your service can request.

3) MPC Server Shares

Multi-Party Computation (MPC) spreads signing power across multiple independent key shares, so no single system ever holds the full private key

How it works in practice:

  • Distributed Key Generation (DKG): participants create linked key shares directly, without ever assembling a complete key at any point.
  • Signing: when a transaction needs approval, each participant produces a partial signature from its share.
  • Combine: those partial signatures merge into a standard ECDSA signature.
  • On-chain result: the signature looks exactly like a normal single-signer ECDSA signature.

Why DKG matters: MPC with DKG is different from Shamir Secret Sharing, where you generate a full key first and then split it. That approach introduces a moment where the complete key exists somewhere, even briefly. DKG avoids that moment entirely by never constructing a whole key in the first place.

Tradeoff: Because shares have to coordinate over the network, MPC typically adds ~100–500ms of latency per signature. That makes it a poor fit for ultra-low-latency use cases like high-frequency trading, but a strong fit for user-facing transactions, automated payments, and batch operations. Para has also optimized signing performance to keep it sub-second in real-world flows.

4) Smart Account Session Keys

Deploy an ERC-4337 smart contract wallet and issue temporary session keys with constrained permissions. The session key lives on your server and signs UserOperations autonomously.

If the session key is compromised, the attacker can only do what the permissions allow. The smart account owner revokes the key with a single transaction.

ERC-7715 standardizes how apps request session key permissions via wallet_grantPermissions. MetaMask Delegation Toolkit and multiple account abstraction providers support it.

Tradeoff: Smart account deployment cost (one-time), higher per-transaction gas for validateUserOp() execution, and bundler infrastructure dependency. Smart accounts also do not translate well cross-chain.

5) TEE-Based Signing

Trusted Execution Environments store private keys inside hardware-isolated enclaves. The key is generated inside the enclave, used for signing inside the enclave, and never leaves in plaintext. The host operating system, hypervisor, and hardware operator cannot access it.

Tradeoff: TEE security depends on hardware trust. Intel SGX has been compromised multiple times (Foreshadow, Plundervolt, AEPIC Leak). AWS Nitro avoids SGX-class side-channel attacks through VM-level isolation, but you are trusting AWS. If the TEE infrastructure goes down, signing stops.

How Do You Build Server-Side Signing with Para?

Para's Server SDK provides two paths to server-side signing, both built on 2-of-2 MPC. The private key is never assembled in a single location.

Step 1: Install the Server SDK

npm install @getpara/server-sdk @getpara/viem-v2-integration --save-exact

Pin the exact version. All @getpara/* packages in your project must use the same version.

Step 2: Pregenerate a Wallet (No User Required)

Wallet pregeneration is the key feature for autonomous server-side signing. You create a wallet for a user identifier (email, phone, custom ID, Twitter handle, or Telegram ID) before the user signs up.

What happens here: Para generates linked MPC key shares. Your server receives the User Share. Para's HSMs hold the Para Share. Your server stores the encrypted User Share in your database.

Before the user claims the wallet, your server holds the User Share. You can sign transactions by coordinating with Para's HSMs. This is effectively server-managed custody of one MPC share. Neither you nor Para alone can reconstruct the full private key, which is stronger than raw key storage. But your server can initiate signing. When the user eventually claims the wallet (client-side only), key rotation transfers the User Share to their device, and your copy becomes invalid.

Step 3: Sign a Transaction

import { createParaAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { http, parseEther, parseGwei } from "viem";
import { base } from "viem/chains";

// Load the encrypted User Share
const encryptedShare = await db.get("alice@example.com");
const decryptedShare = await decrypt(encryptedShare);

// Set the share on the Para instance
await para.setUserShare(decryptedShare);

// Create a Viem client (standard Viem API from here)
const account = createParaAccount(para);
const client = createParaViemClient(para, {
  account,
  chain: base,
  transport: http("https://mainnet.base.org"),
});

// Sign and send (MPC runs between your server and Para's HSMs)
const request = await client.prepareTransactionRequest({
  account,
  to: "0xRecipientAddress",
  value: parseEther("0.01"),
  chain: base,
});

const hash = await client.sendTransaction(request);

The signing call looks identical to standard Viem code. Under the hood, createParaAccount wraps the MPC protocol. Your server sends the transaction hash to Para's infrastructure. Both parties compute partial signatures from their shares. The partial signatures combine into a valid ECDSA signature.

Alternative: Session Import (User Authenticates First)

If the user has already authenticated client-side, they can export their session to your server:

// Client-side: user exports session after authentication
const sessionString = await clientPara.exportSession();

// Server-side: import the session
const serverPara = new ParaServer(Environment.BETA, process.env.PARA_API_KEY);
await serverPara.importSession(sessionString);

// Sign transactions using the imported session
const isActive = await serverPara.isSessionActive();

Call keepSessionAlive() to extend a session.

Alternative: REST API (Any Language)

For Python, Go, Ruby, or any HTTP client:

import requests, os

BASE_URL = "https://api.beta.getpara.com"
headers = {
    "X-API-Key": os.environ["PARA_API_KEY"],
    "Content-Type": "application/json",
}

# Create a wallet
wallet = requests.post(f"{BASE_URL}/v1/wallets", headers=headers, json={
    "type": "EVM",
    "userIdentifier": "alice@example.com",
    "userIdentifierType": "EMAIL",
}).json()

# Sign a transaction
sig = requests.post(
    f"{BASE_URL}/v1/wallets/{wallet['id']}/sign-transaction",
    headers=headers,
    json={"transaction": {"to": "0x...", "chainId": 8453, "value": "0x2386F26FC10000"}},
).json()

Para's REST API supports wallet creation (/v1/wallets), raw signing (/v1/wallets/{id}/sign-raw), and transaction signing (/v1/wallets/{id}/sign-transaction).

Hybrid Architectures are the Production Standard

No production system relies on a single pattern. A typical gaming platform might combine:

  • Treasury: Gnosis Safe multi-sig (3-of-5 signers for high-value transfers)
  • Game server: Para Server SDK with MPC (policy-restricted to mint() and transfer(), daily limit of 10,000 USDC)
  • Player wallets: Para embedded wallets (client-side MPC for player actions)
  • Relayer: AWS KMS (rate-limited gasless meta-transactions for new players)

Each layer has different security requirements and cost profiles. The treasury prioritizes security over speed. The game server prioritizes throughput. The relayer prioritizes cost efficiency.

How to Choose a Server-Side Signing Pattern

Pick based on the failure mode you cannot tolerate:

  • If you cannot survive a key leak, avoid raw keys. Favor MPC or smart-account permissions.
  • If permissions and blast radius control matter most, session keys on smart accounts with MPC shine.
  • If hardware trust and availability are acceptable tradeoffs, TEEs are a viable alternative.

Most teams land on a hybrid setup. The key is designing the boundaries intentionally, then enforcing policy at every signing surface.


For implementation details and full code examples, see Para's Server SDK documentation and the examples-hub repository.