StableOps

签名器

校验短时执行授权,并使用本地钱包私钥或 AWS KMS 完成受控签名。

@stableops/agent-signer 是客户自行部署的签名组件。它不会接受任意消息,而是先验证 StableOps 签发的短时执行授权,再根据网络签署 EIP-3009、Permit2 或 Solana 付款载荷。

执行授权会绑定 Agent、钱包、网络、资产、收款地址、金额、随机数和有效期。任何字段不一致、授权过期、签发密钥未知或授权重放冲突,都会在使用钱包私钥前被拒绝。

钱包登记与付款签名是两件事

钱包登记时,运营者代码先调用管理客户端领取一次性质询,再用钱包账户的 signMessage 签署完整质询文本。登记成功后,签名器伴随服务才负责处理付款执行授权。

伴随服务的 /v1/sign 只接受 StableOps 执行授权,不能用于签署钱包登记质询或其它任意消息。不要为了复用接口而给它增加通用签名能力。

完整的钱包登记代码见快速开始的 2.3 节

本地签名器

LocalTestSigner 仅用于沙盒 EVM 测试网络。启动前必须准备:

  • 与已登记付款地址匹配的钱包私钥。
  • StableOps 当前执行授权密钥标识及对应 Ed25519 公钥。
  • 高熵伴随服务鉴权令牌。
  • 可持久化且权限受限的授权记录文件。
import { readFileSync } from 'node:fs'
import {
  FileGrantAuthorizationStore,
  LocalTestSigner,
  startSignerSidecar,
} from '@stableops/agent-signer'

function required(name: string): string {
  const value = process.env[name]?.trim()
  if (!value) throw new Error(`缺少环境变量 ${name}`)
  return value
}

const signer = new LocalTestSigner({
  privateKey: required('BASE_SEPOLIA_TEST_PRIVATE_KEY') as `0x${string}`,
  environment: 'SANDBOX',
  network: 'eip155:84532',
  grantVerification: {
    publicKeys: {
      [required('STABLEOPS_GRANT_KEY_ID')]: readFileSync(
        required('STABLEOPS_GRANT_PUBLIC_KEY_FILE'),
        'utf8',
      ),
    },
  },
  store: new FileGrantAuthorizationStore('./data/grant-authorizations.json'),
})

const { url } = await startSignerSidecar({
  signer,
  host: '127.0.0.1',
  port: 8789,
  authToken: required('STABLEOPS_SIDECAR_TOKEN'),
})

console.log(`签名器伴随服务已启动:${url}`)

在 Base Sepolia 以外的沙盒 EVM 网络中,仍应显式设置 environment: 'SANDBOX' 和对应测试网的 CAIP-2 标识。正式环境 EVM 网络使用下文的 AwsKmsSigner。签名器会同时校验执行授权中的环境、网络和钱包地址。

Solana 使用 LocalSvmSigner.create 创建 Ed25519 签名器。私钥使用 Base58 编码,网络需明确指定为 Solana 主网或 Devnet:

import { readFileSync } from 'node:fs'
import {
  FileGrantAuthorizationStore,
  LocalSvmSigner,
} from '@stableops/agent-signer'

function required(name: string): string {
  const value = process.env[name]?.trim()
  if (!value) throw new Error(`缺少环境变量 ${name}`)
  return value
}

const signer = await LocalSvmSigner.create({
  privateKey: required('SOLANA_PRIVATE_KEY_BASE58'),
  environment: 'LIVE',
  network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
  grantVerification: {
    publicKeys: {
      [required('STABLEOPS_GRANT_KEY_ID')]: readFileSync(
        required('STABLEOPS_GRANT_PUBLIC_KEY_FILE'),
        'utf8',
      ),
    },
  },
  store: new FileGrantAuthorizationStore('./data/grant-authorizations.json'),
})

签名器会使用付款要求中的结算服务费用支付方生成部分签名交易,因此付款钱包无需持有 SOL 燃料费。

伴随服务提供 GET /healthPOST /v1/signstartSignerSidecar 会拒绝非回环监听地址。即使如此,也必须配置鉴权令牌,防止本机其它进程调用签名接口。

FileGrantAuthorizationStore 在进程重启后仍能识别已使用授权。不要使用临时目录,也不要在多个签名器副本之间共享一个不具备并发协调能力的本地文件。多副本部署应改用具备原子写入和唯一约束的共享存储实现。

AWS KMS 签名器

正式环境 EVM 部署应把私钥放在 AWS KMS。AwsKmsSigner 要求用途为 SIGN_VERIFY、密钥规格为 ECC_SECG_P256K1,并支持 ECDSA_SHA_256

import { readFileSync } from 'node:fs'
import {
  AwsKmsSigner,
  FileGrantAuthorizationStore,
} from '@stableops/agent-signer'

function required(name: string): string {
  const value = process.env[name]?.trim()
  if (!value) throw new Error(`缺少环境变量 ${name}`)
  return value
}

const signer = await AwsKmsSigner.create({
  keyId: required('STABLEOPS_KMS_KEY_ID'),
  walletAddress: required('STABLEOPS_WALLET_ADDRESS') as `0x${string}`,
  environment: 'LIVE',
  network: 'eip155:8453',
  clientConfig: { region: process.env.AWS_REGION },
  grantVerification: {
    publicKeys: {
      [required('STABLEOPS_GRANT_KEY_ID')]: readFileSync(
        required('STABLEOPS_GRANT_PUBLIC_KEY_FILE'),
        'utf8',
      ),
    },
  },
  store: new FileGrantAuthorizationStore('./data/grant-authorizations.json'),
})

启动时,签名器会读取 KMS 公钥并推导 EVM 地址。若与 walletAddress 不一致会立即失败。发送给 KMS 的内容是摘要,MessageTypeDIGEST。签名器还会校验 DER 输出、把高 s 签名规范化为低 s,并验证恢复地址。

AWS KMS 仅支持 EVM 网络。正式环境还必须通过 StableOps 的组织主网风控门禁。

每个网络应使用独立的签名器实例、钱包和授权记录存储,并显式设置 network。不要让同一个实例接受多个网络的执行授权。

部署检查

  • 伴随服务只能监听回环地址;startSignerSidecar 会拒绝非回环地址。禁止将其暴露到公网。
  • Agent 运行时只持有伴随服务地址和鉴权令牌,不持有钱包私钥或 KMS 管理权限。
  • 执行授权公钥和密钥标识来自可信接入资料,并在轮换时同时更新。
  • 授权记录持久化、定期备份,且不允许多个实例绕过防重放约束。
  • 日志只记录授权标识、钱包地址和错误类别,不输出私钥、完整执行授权或支付签名。

这篇文档怎么样?

最后更新

本页内容