Provider-agnostic TypeScript signing library for keys that live outside the application process.
Phase 1 supports EVM signing with Google Cloud KMS asymmetric EC_SIGN_SECP256K1_SHA256 keys using SOFTWARE or HSM protection levels.
pnpm add @avacuscc/external-signer @avacuscc/external-signer-gcp-kms@avacuscc/external-signer: core runtime, profiles, stable errors, and EVM crypto utilities.@avacuscc/external-signer-gcp-kms: Google Cloud KMS runtime provider and provisioning manager.
Core does not import cloud SDKs. GCP users install the GCP package; future AWS KMS or custody users can install separate provider packages.
import { ExternalSigner, evmProfile } from '@avacuscc/external-signer';
import { GcpKmsKeyProvider } from '@avacuscc/external-signer-gcp-kms';
const signer = new ExternalSigner({
provider: new GcpKmsKeyProvider({
projectId: process.env.GCP_PROJECT_ID,
location: process.env.GCP_KMS_LOCATION,
keyRing: process.env.GCP_KMS_KEYRING,
cryptoKey: process.env.GCP_KMS_CRYPTO_KEY,
keyRef: process.env.GCP_KMS_KEY_VERSION,
credentialsPath: process.env.GOOGLE_APPLICATION_CREDENTIALS
}),
profile: evmProfile()
});
const address = await signer.getAddress();
const signature = await signer.signMessage('hello');
console.log(signature.r);
console.log(signature.s);
console.log(signature.v);
console.log(signature.serializedSignature);signMessage() uses the selected profile's message semantics. For evmProfile(), it applies EIP-191 and keccak256 before asking the provider to sign the digest.
EVM signatures return this shape:
type EvmSignature = {
r: `0x${string}`;
s: `0x${string}`;
v: 27 | 28;
serializedSignature: `0x${string}`;
digest: `0x${string}`;
recoveredAddress: `0x${string}`;
};Use serializedSignature for ethers.verifyMessage() or wallet-compatible 65-byte signature flows. Use r, s, and v when an API or contract needs split ECDSA fields.
const signature = await signer.signDigest('0x0000000000000000000000000000000000000000000000000000000000000000');signDigest() signs exactly 32 bytes. It is for callers that already computed the digest. The library intentionally does not expose ambiguous signData().
import { KeyManagementServiceClient } from '@google-cloud/kms';
import { evmProfile } from '@avacuscc/external-signer';
import { GcpKmsKeyManager, GcpKmsPresets, GcpKmsProtectionLevels } from '@avacuscc/external-signer-gcp-kms';
const kmsClient = new KeyManagementServiceClient({
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS
});
const manager = new GcpKmsKeyManager({
projectId: 'project-id',
location: 'asia-northeast1',
client: kmsClient
});
const { key, signer } = await manager.createSigner({
keyRing: 'wallets',
cryptoKey: 'wallet-001',
preset: GcpKmsPresets.Evm,
protectionLevel: GcpKmsProtectionLevels.Hsm,
createKeyRingIfMissing: true,
profile: evmProfile()
});createSigner() creates the key and returns a signer that reuses the manager's auth/client. Service code should not configure GCP auth twice.
const signer = manager.getSigner({
keyRing: 'wallets',
cryptoKey: 'wallet-001',
keyVersion: '2',
profile: evmProfile()
});
const signature = await signer.signMessage('hello');
console.log(signature.r, signature.s, signature.v);Do not assume key version 1. Store the selected key version in app configuration or database records.
const wallets = await manager.listWallets({
keyRing: 'wallets',
cryptoKey: 'wallet-001',
profile: evmProfile(),
onlyEnabled: true
});Wallet discovery derives addresses from enabled KMS key versions through the supplied profile.
import { GcpKmsAlgorithms, GcpKmsPresets, GcpKmsProtectionLevels } from '@avacuscc/external-signer-gcp-kms';
await manager.createSigningKey({
keyRing: 'wallets',
cryptoKey: 'wallet-001',
preset: GcpKmsPresets.Evm,
protectionLevel: GcpKmsProtectionLevels.Software
});
await manager.createSigningKey({
keyRing: 'wallets',
cryptoKey: 'wallet-002',
algorithm: GcpKmsAlgorithms.EvmSecp256k1Sha256,
protectionLevel: GcpKmsProtectionLevels.Hsm
});Use presets for common cases. Use algorithm when you want exact GCP configuration. Do not pass both.
Auth priority for @avacuscc/external-signer-gcp-kms:
- Injected
KeyManagementServiceClient. - Explicit
credentialsorcredentialsPath. - Google ADC, Workload Identity, Cloud Run/GKE metadata credentials, or other Google-supported default auth.
Server examples should use an injected client or credentialsPath. Google ADC is supported, but it is not the primary backend-server setup because servers should not depend on manually running gcloud auth application-default login.
Credential path mode:
const manager = new GcpKmsKeyManager({
projectId: 'project-id',
location: 'asia-northeast1',
credentialsPath: process.env.GOOGLE_APPLICATION_CREDENTIALS
});Injected client mode:
import { KeyManagementServiceClient } from '@google-cloud/kms';
const client = new KeyManagementServiceClient({
keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS
});
const manager = new GcpKmsKeyManager({
projectId: 'project-id',
location: 'asia-northeast1',
client
});pnpm test
pnpm typecheck
pnpm buildCurrent test coverage includes core errors, hex/digest utilities, public-key parsing, DER/raw signature conversion, EVM profile signing, ExternalSigner, GCP KMS paths, presets, runtime provider, and key manager. Live GCP KMS coverage is opt-in with GCP_KMS_INTEGRATION=1.
Minimum runtime example:
cp examples/node-runtime-sign/.env.example examples/node-runtime-sign/.env
pnpm --filter @avacuscc/external-signer-example-node-runtime-sign sign:message
pnpm --filter @avacuscc/external-signer-example-node-runtime-sign sign:digestSee examples/node-runtime-sign/README.md. The example uses an existing GCP KMS key version and does not create keys or broadcast transactions.
Use injected clients or explicit credentialsPath for backend servers. ADC/Workload Identity remains supported for Google-managed runtime environments.
Core has no cloud SDK dependency. Provider packages own cloud-specific auth, paths, algorithms, and provisioning.
Future packages can add AWS KMS, custody APIs, private-key adapters, Bitcoin, Solana, and EIP-712 without renaming the core package.
The first library version is 0.1.0. See CHANGELOG.md for release notes.