Thanks to visit codestin.com
Credit goes to github.com

Skip to content

wakumo/external-signer

Repository files navigation

External Signer

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.

Install

pnpm add @avacuscc/external-signer @avacuscc/external-signer-gcp-kms

Packages

  • @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.

Runtime Signing

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.

Digest Signing

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().

Key Creation

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.

Existing Key

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.

List Wallets

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.

Friendly Presets And Low-Level Options

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.

Authentication

Auth priority for @avacuscc/external-signer-gcp-kms:

  1. Injected KeyManagementServiceClient.
  2. Explicit credentials or credentialsPath.
  3. 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
});

Tests

pnpm test
pnpm typecheck
pnpm build

Current 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.

Examples

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:digest

See 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.

Versioning

The first library version is 0.1.0. See CHANGELOG.md for release notes.

About

Provider-agnostic TypeScript signing library for keys that live outside the application process.

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors