mpaya5/aws_kms_lambda_bsc
GitHub: mpaya5/aws_kms_lambda_bsc
基于 AWS KMS 与 Lambda 的 BSC 链交易签名服务,确保后端应用在不接触原始私钥的前提下安全完成 Web3 交易签名。
Stars: 1 | Forks: 0
# 适用于 BSC 的 AWS KMS Lambda 签名器
基于 AWS KMS/Lambda 的签名基础设施,适配于 **BSC**,专注于**安全的 Web3 后端交易工作流**。
核心目标:**避免在应用服务中暴露原始私钥**。您的后端构建未签名的交易,并将其委托给调用 **AWS KMS**(`ECC_SECG_P256K1`)的独立 Lambda 函数进行签名。后端的私钥数据绝不会出现在环境变量、请求 payload 或应用程序内存中。
本仓库**扩展**了上游 AWS Ethereum 示例以支持 BSC。它不是一个被动的 fork:它适配了 BSC 后端签名工作流的链配置、交易组装和安全文档。请参阅 [AWS-README.md](AWS-README.md) 了解原始的 Ethereum 深入解析。
上游:[aws-samples/aws-kms-ethereum-accounts](https://github.com/aws-samples/aws-kms-ethereum-accounts)。
## 我的贡献 / BSC 适配
- 添加了 BSC / Chapel 链配置(`ETH_NETWORK`、`CHAIN_ID`,主网 `56` / 测试网 `97`)。
- 为 BSC 的 Gas 模型适配了传统交易组装(`gas` + `gasPrice`,无 EIP-1559 type-2)。
- 针对防重放保护调整了 EIP-155 签名和 `chainId` 处理。
- 添加了关于 BSC 签名的演示示例和测试(`examples/`、`tests/test_signing_core.py`)。
- 记录了后端签名工作流的威胁模型和安全假设。
- 移除了传统的 `privateKey` / `SKEYS` payload 模式,全面采用仅通过 KMS 进行签名。
## 本仓库不包含什么
- **不**是托管的资产托管解决方案。
- **未**经过审计。
- **不**是钱包。
- **不**旨在绕过运维控制。
- 在没有 IAM 加固、监控和密钥生命周期管理的情况下,**不**具备生产环境可用性。
## 快速开始(测试)
```
git clone && cd aws_kms_lambda_bsc
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest -v
```
预期结果:在 `tests/test_signing_core.py` 中 **11 项通过**(EIP-2、EIP-155、chain id 解析、tx 验证、拒绝包含秘密字段)。
## 架构
```
sequenceDiagram
participant Backend as Application backend
participant Lambda as Lambda signer
participant KMS as AWS KMS (CMK)
participant RPC as BSC RPC node
Backend->>Lambda: unsigned tx (to, nonce, gas, gasPrice, data, value)
Lambda->>KMS: GetPublicKey
KMS-->>Lambda: DER public key
Lambda->>Lambda: derive BSC address, hash unsigned tx
Lambda->>KMS: Sign(digest)
KMS-->>Lambda: ECDSA (r, s)
Lambda->>Lambda: recover v, assemble EIP-155 signed tx
Lambda-->>Backend: signed_tx (rawTransaction, hash, r, s, v)
Backend->>RPC: eth_sendRawTransaction (optional)
```
| 组件 | 职责 |
|-----------|----------------|
| **Backend** | 业务逻辑、nonce 管理、tx 组装 —— **无私钥** |
| **Lambda 签名器** | 对未签名的 tx 进行哈希处理,协调 KMS 签名,应用 EIP-155 `v` |
| **AWS KMS CMK** | 持有 secp256k1 密钥对;仅向 Lambda 暴露 `Sign` / `GetPublicKey` |
| **BSC RPC** | 广播已签名的原始交易(建议在测试网上进行演示) |
## 针对 BSC 的更改
上游 AWS 示例针对的是 Ethereum。此 fork 保留了 **KMS 签名模型**并调整了特定于链的交易参数:
| 主题 | Ethereum(典型) | BSC(本仓库) |
|-------|-------------------|-----------------|
| **chainId** | `1` 主网,`5` Goerli(历史),`11155111` Sepolia | `56` 主网,`97` Chapel 测试网 |
| **Gas 模型** | L1 上通常为 EIP-1559(`maxFeePerGas`、`maxPriorityFeePerGas`) | 传统 `gas` + `gasPrice`(PoSA 链) |
| **Tx 组装** | 用于 EIP-1559 交易(tx)的 Type-2 字段 | 通过 `serializable_unsigned_transaction_from_dict` 进行传统 RLP 编码 |
| **地址生成** | KMS 公钥的 Keccak-256 哈希(相同的 secp256k1 曲线) | 完全相同 —— BSC 使用与 Ethereum 相同的账户模型 |
| **重放保护** | EIP-155:`v = recovery + chainId * 2 + 35` | 使用 BSC `chainId` 的相同公式 |
在部署时使用 `ETH_NETWORK`(`bsc`、`bsc-testnet`、`chapel`)配置网络,或使用 `CHAIN_ID` 进行覆盖。
## 威胁模型
| 威胁 | 此设计中的缓解措施 | 残余风险 |
|--------|---------------------------|---------------|
| 从后端窃取私钥 | 后端从不持有或传输密钥材料 | 被攻破的后端如果能够调用 Lambda,仍然可以**请求任意签名** |
| 从 Lambda 提取密钥 | 密钥保留在 KMS HSM 中;Lambda 仅接收签名 | Lambda 代码漏洞或过度的 IAM 权限可能会削弱隔离性 |
| 未经授权的签名 | 通过 IAM 限制 `lambda:InvokeFunction` 和 `kms:Sign` | 配置错误的资源策略是常见的故障模式 |
| 跨链重放 | EIP-155 将 `chainId` 嵌入签名中 | 错误的 `CHAIN_ID` 环境变量可能会产生有效但非预期链的 tx |
| 日志泄露 | Lambda 仅记录事件**键名**;默认 `LOG_LEVEL=WARNING` | 提高日志级别或自定义处理程序可能仍会泄露 payload |
| 内部滥用 | KMS CloudTrail + Lambda 调用审计 | 需要此处未包含的运维监控 |
## 安全假设
1. **KMS 是签名密钥的唯一托管方**(`ECC_SECG_P256K1`、`SIGN_VERIFY`)。
2. **只有签名器 Lambda** 对 CMK 拥有 `kms:Sign` 和 `kms:GetPublicKey` 权限。
3. **后端调用方**已经过身份验证(IAM、API Gateway 授权方、VPC 等)—— 本演示中未完全实现。
4. **交易意图**在调用前已进行链下验证(接收者、数值、合约白名单)。
5. 假定使用**传统 Gas 交易**(`gas` + `gasPrice`);EIP-1559 type-2 交易(tx)不在范围内。
6. **BSC PoSA 共识**和 RPC 端点仅受信任用于广播;签名不依赖 RPC。
7. 运维人员接受**演示级别**的错误处理、可观测性和密钥生命周期实践。
## 部署 (AWS CDK)
前置条件:AWS 账户、CDK CLI、Python 3.9+、Node.js。
```
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cdk bootstrap # once per account/region
cdk deploy
```
CDK 预置内容:
- KMS CMK (`ECC_SECG_P256K1`)
- 带有 `KMS_KEY_ID` 和 `ETH_NETWORK` 环境变量的 Python 3.9 Lambda
- IAM 授权:Lambda → `kms:GetPublicKey`、`kms:Sign`
完成后的销毁操作:
```
cdk destroy
```
对于测试网演示,请使用 Chapel 进行部署:
```
# app.py — 实例化 stack 时传入 eth_network
AwsKmsLambdaEthereumStack(app, "aws-kms-lambda-ethereum", eth_network="bsc-testnet")
```
## 本地演示 / 仅限测试网
**所有本地和 CI 实验请使用 BSC Chapel 测试网(`chainId` 97)。**
1. 部署堆栈(首选 `eth_network="bsc-testnet"`)。
2. 从 Lambda 日志读取 KMS 生成的地址,或者通过使用空运行辅助程序进行调用来读取。
3. 通过 [Chapel 水龙头](https://testnet.bnbchain.org/faucet-smart)为该地址充值。
4. 复制 [`.env.example`](.env.example) → `.env` 并设置 `AWS_LAMBDA_FUNCTION_NAME` 和 `AWS_REGION`。
5. 运行示例客户端:
```
pip install -r requirements-dev.txt
python examples/lambda_signer_client.py
```
6. 使用 `eth_sendRawTransaction` 针对 Chapel RPC(`.env.example` 中的 `BSC_RPC_URL`)广播 `signed_tx.rawTransaction`。
**未经**全面安全审查,请勿将此设置用于主网资产。
## 后端集成(安全客户端)
后端仅发送**未签名的交易字段**。密钥材料(`privateKey`、助记词、种子、`SKEYS`)绝**不能**出现在 payload 中 —— 签名器会在客户端和 Lambda 上同时拒绝它们。
### 示例:未签名输入 → 已签名输出
**Lambda 调用 payload(仅限未签名 tx):**
```
{
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"nonce": 3,
"gas": 21000,
"gasPrice": 10000000000,
"value": 0,
"data": "0x"
}
```
没有 `privateKey`。调用者也不提供 `chainId` —— 它是在服务端由 `ETH_NETWORK` / `CHAIN_ID` 设置的。
**Lambda 响应(已清理):**
```
{
"signed_tx": {
"rawTransaction": "0xf86c...a1b2",
"hash": "0x9c3e...4f80",
"r": 1234567890123456789012345678901234567890123456789012345678901234,
"s": 9876543210987654321098765432109876543210987654321098765432109,
"v": 230,
"from": "0xAb12...cD34",
"chainId": 97
}
}
```
十六进制值为了文档说明进行了截断。在 Chapel 测试网上使用 `rawTransaction` 和 `eth_sendRawTransaction`。
```
import boto3, json, os
client = boto3.client("lambda", region_name=os.getenv("AWS_REGION"))
payload = {
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0",
"nonce": 0,
"gas": 21000,
"gasPrice": 10_000_000_000,
"value": 0,
"data": "0x",
}
response = client.invoke(
FunctionName=os.environ["AWS_LAMBDA_FUNCTION_NAME"],
InvocationType="RequestResponse",
Payload=json.dumps(payload),
)
result = json.load(response["Payload"])
signed = result["signed_tx"]
```
带有更完善防护机制的完整客户端位于 [`examples/lambda_signer_client.py`](examples/lambda_signer_client.py)。
### Lambda 事件 schema
| 字段 | 必需 | 描述 |
|-------|----------|-------------|
| `to` | 是 | 接收方地址 |
| `nonce` | 是 | 账户 nonce |
| `gas` | 是 | Gas 限制 |
| `gasPrice` | 是 | 传统 Gas 价格 (单位为 wei) |
| `data` | 是 | 调用数据(转账时为 `0x`) |
| `value` | 否 | 发送的 wei 数量(默认为 `0`) |
`chainId` 取自 Lambda 环境(`ETH_NETWORK` / `CHAIN_ID`),而不是来自调用方 —— 防止不受信任的输入造成跨链混淆。
## 环境变量
请参阅 [`.env.example`](.env.example)。Lambda 端的变量由 CDK 设置:
| 变量 | 设置者 | 用途 |
|----------|--------|---------|
| `KMS_KEY_ID` | CDK | 用于签名的 CMK |
| `ETH_NETWORK` | CDK | `bsc`、`bsc-testnet` 或 `chapel` |
| `CHAIN_ID` | 可选覆盖 | 显式的 EIP-155 chain id |
| `LOG_LEVEL` | CDK / 运维 | 日志详细级别 |
## 测试
运行完整的单元测试套件:
```
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytest -v
# 或:pytest tests/test_signing_core.py -v
```
`tests/test_signing_core.py` 中的覆盖范围:
- EIP-2 低 `s` 标准化
- EIP-155 `v` 值计算(BSC 主网 / Chapel 测试网)
- Chain id 解析(`bsc`、`bsc-testnet`、`chapel`、`CHAIN_ID` 覆盖)
- Tx 参数验证(必填字段,无秘密字段)
- 拒绝传入事件中的 `privateKey` / `SKEYS`
Lambda 集成测试需要 Python 3.9 以及部署时捆绑的 `eth_client` 依赖项。
## 项目布局
```
app.py # CDK entrypoint
aws_kms_lambda_ethereum/
aws_kms_lambda_ethereum_stack.py
_lambda/functions/eth_client/
lambda_function.py # handler
lambda_helper.py # KMS signing orchestration
signing_core.py # Pure helpers (chain id, EIP-155, tx params)
examples/
lambda_signer_client.py # backend client (no private keys)
tests/
test_signing_core.py
```
## 延伸阅读
- [AWS 博客 —— KMS Ethereum 账户(第 1 部分)](https://aws.amazon.com/blogs/database/part1-use-aws-kms-to-securely-manage-ethereum-accounts/)
- [AWS 博客 —— KMS Ethereum 账户(第 2 部分)](https://aws.amazon.com/blogs/database/part2-use-aws-kms-to-securely-manage-ethereum-accounts/)
- [BSC Chapel 测试网](https://testnet.bnbchain.org/)
## 许可证
MIT-0 —— 请参阅 [LICENSE](LICENSE)。
标签:AWS KMS, BSC, Web3, 交易签名, 区块链, 后端开发, 逆向工具