hyperledger-firefly / hyperledger-firefly/signer
Add AWS KMS signing backend
- Dominant language
- Go
- Stars
- 14
- Forks
- 19
- Avg merge
- 17h 12m
- Merged PRs (30d)
- 3
Description
## Feature Request: Add AWS KMS signing backend
### Summary
Add an AWS KMS signing backend so the FireFly Signer can sign Ethereum transactions using keys stored in AWS KMS, without the key material ever leaving the KMS hardware.
### Motivation
Currently, FireFly Signer only supports a file-based wallet (`fileWallet`) where signing keys live on disk. For enterprise and regulated deployments (financial services, tokenised assets, etc.), there is a strong requirement to use hardware-backed key management:
- **Key isolation:** the private key never exists in process memory or on disk — signing happens inside KMS.
- **FIPS 140-2 Level 3 compliance:** KMS is FIPS-validated, which institutional auditors require.
- **Key rotation without restart:** KMS keys can be rotated at the AWS level without touching the signer.
- **Audit trail:** every signing operation is logged in CloudTrail with the KMS key ID.
- **IRSA integration:** on EKS, the signer pod can use IAM Roles for Service Accounts to authenticate to KMS — no long-lived credentials.
AWS KMS supports asymmetric keys on the `ECC_SECGP256K1` curve (the secp256k1 curve used by Ethereum), so KMS can produce valid Ethereum signatures.
### Proposed Implementation
I have a working implementation ready as a PR. It adds:
1. **`pkg/kmswallet/`** — a new `KMSWallet` type that implements the existing `ethsigner.Wallet` interface. Internally it:
- Calls `kms:GetPublicKey` at startup to derive the Ethereum address from the KMS public key.
- Calls `kms:Sign` with `MessageType: Digest` and `SigningAlgorithm: ECDSA_SHA_256` for each signing request.
- Parses the DER-encoded ECDSA signature to extract R and S.
- Recovers the Y-parity (V) by trying both 27 and 28 and checking which recovers to the known public key (KMS does not return a recovery ID).
2. **`pkg/kmswallet/kmssigner.go`** — a `kmsSigner` that implements the `secp256k1.Signer` and `secp256k1.SignerDirect` interfaces, so it slots into the existing transaction signing path without changes.
3. **Config section `kmsWallet`** alongside `fileWallet`:
```yaml
kmsWallet:
enabled: true
keyId:
region:
endpoint:
```
When `kmsWallet.enabled` is true, it takes precedence over `fileWallet`. Defaults to false (backwards-compatible).
### Design Decisions
- **V recovery by trial:** KMS returns a DER ECDSA signature without a recovery ID. The implementation tries both parities (V=27, V=28) and checks which one recovers to the KMS public key's address. This is the same approach used by other KMS-backed Ethereum signers.
- **Pre-hashed messages:** the signer receives the already-hashed (Keccak-256) transaction payload and passes it to KMS as a `Digest`. KMS does not re-hash it.
- **Backwards-compatible:** `fileWallet` remains the default. `kmsWallet` is opt-in.
- **No multi-key support (yet):** the initial implementation supports one KMS key per signer instance (one address in `eth_accounts`). Multi-key support can be added later by mapping addresses to KMS key IDs.
### Testing
The PR includes the implementation and passes all existing tests. I plan to add integration tests with LocalStack (via the `endpoint` config) in a follow-up if there's interest from maintainers.
### Open Questions for Maintainers
1. Is there appetite for this feature in the project? I'm happy to maintain it long-term.
2. Would you prefer the V-recovery approach, or should we explore whether KMS can return the recovery ID via a different API?
3. Any preference on the config structure — should `kmsWallet` be under a broader `wallets` section, or is a top-level section fine?
Contributor guide
Research direction
Start by reading the existing fileWallet path and the ethsigner.Wallet and secp256k1 signer interfaces, then review the proposed pkg/kmswallet/ and pkg/kmswallet/kmssigner.go entry points. Verify the kmsWallet configuration, KMS public-key and signing flow, V recovery, and compatibility with existing tests; done means the feature is tested without changing the fileWallet default.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, go
- Domain
- backend, cloud, cryptography, security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100