Skip to content

Commit 0de939b

Browse files
nasjuiceamilz
andauthored
feat: add GCP signer integration (#29)
* feat: add GCP signer integration --------- Co-authored-by: amilz <85324096+amilz@users.noreply.github.com>
1 parent d4282b1 commit 0de939b

File tree

20 files changed

+2159
-9
lines changed

20 files changed

+2159
-9
lines changed

.env.example

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ your-rsa-4096-private-key-here
2828
-----END PRIVATE KEY-----"
2929
FIREBLOCKS_VAULT_ACCOUNT_ID=0
3030
# FIREBLOCKS_ASSET_ID=SOL # Optional, defaults to SOL (use SOL_TEST for devnet)
31-
# FIREBLOCKS_API_BASE_URL=https://api.fireblocks.io # Optional, defaults to this
31+
# FIREBLOCKS_API_BASE_URL=https://api.fireblocks.io # Optional, defaults to this
32+
33+
# GCP KMS Integration Tests (uses Application Default Credentials)
34+
GCP_KMS_KEY_NAME=projects/your-project/locations/us-east1/keyRings/your-keyring/cryptoKeys/your-key/cryptoKeyVersions/1
35+
GCP_KMS_SIGNER_PUBKEY=YourSolanaPublicKeyBase58Here

.github/workflows/ci.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
matrix:
1717
sdk_version: [v2, v3]
18-
backend: [memory, vault, privy, turnkey, all]
18+
backend: [memory, vault, privy, turnkey, aws_kms, fireblocks, gcp_kms, all]
1919
steps:
2020
- uses: actions/checkout@v4
2121
- uses: dtolnay/rust-toolchain@stable
@@ -37,10 +37,15 @@ jobs:
3737
strategy:
3838
matrix:
3939
sdk_version: [v2, v3]
40-
test: [test_privy_integration, test_turnkey_integration, test_fireblocks_integration]
40+
test: [test_privy_integration, test_turnkey_integration, test_fireblocks_integration, test_gcp_kms_integration]
4141
steps:
4242
- uses: actions/checkout@v4
4343
- uses: dtolnay/rust-toolchain@stable
44+
- name: Setup GCP credentials
45+
if: matrix.test == 'test_gcp_kms_integration'
46+
uses: google-github-actions/auth@v2
47+
with:
48+
credentials_json: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}
4449
- name: Run ${{ matrix.test }} with SDK ${{ matrix.sdk_version }}
4550
env:
4651
PRIVY_APP_ID: ${{ secrets.PRIVY_APP_ID }}
@@ -54,6 +59,8 @@ jobs:
5459
FIREBLOCKS_API_KEY: ${{ secrets.FIREBLOCKS_API_KEY }}
5560
FIREBLOCKS_PRIVATE_KEY_PEM: ${{ secrets.FIREBLOCKS_PRIVATE_KEY_PEM }}
5661
FIREBLOCKS_VAULT_ACCOUNT_ID: ${{ secrets.FIREBLOCKS_VAULT_ACCOUNT_ID }}
62+
GCP_KMS_KEY_NAME: ${{ secrets.GCP_KMS_KEY_NAME }}
63+
GCP_KMS_SIGNER_PUBKEY: ${{ secrets.GCP_KMS_SIGNER_PUBKEY }}
5764
SOLANA_RPC_URL: https://api.devnet.solana.com
5865
run: |
5966
if [ "${{ matrix.sdk_version }}" = "v2" ]; then

CLAUDE.md

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
44

55
## Project Overview
66

7-
`solana-keychain` is a Rust library providing a unified interface for signing Solana transactions across multiple backend implementations. The architecture centers around a single `SolanaSigner` trait that abstracts over four different signing backends: Memory (local keypairs), Vault (HashiCorp), Privy, and Turnkey.
7+
`solana-keychain` is a Rust library providing a unified interface for signing Solana transactions across multiple backend implementations. The architecture centers around a single `SolanaSigner` trait that abstracts over seven different signing backends: Memory (local keypairs), Vault (HashiCorp), Privy, Turnkey, AWS KMS, Fireblocks, and GCP KMS.
88

99
## Common Commands
1010

@@ -24,6 +24,9 @@ cd rust && cargo test --features memory
2424
cd rust && cargo test --features vault
2525
cd rust && cargo test --features privy
2626
cd rust && cargo test --features turnkey
27+
cd rust && cargo test --features aws_kms
28+
cd rust && cargo test --features fireblocks
29+
cd rust && cargo test --features gcp_kms
2730

2831
# Run a single test
2932
cd rust && cargo test test_name --all-features
@@ -136,6 +139,23 @@ All signers follow a consistent pattern but differ in where keys are stored:
136139
- Response contains r,s signature components that must be padded to 32 bytes each
137140
- Availability checked via `whoami` endpoint
138141

142+
5. **AWS KMS** ([rust/src/aws_kms/mod.rs](rust/src/aws_kms/mod.rs))
143+
- Uses AWS SDK with EdDSA (Ed25519) signing
144+
- Automatic credential discovery via environment or IAM
145+
- Availability checked via `DescribeKey`
146+
147+
6. **FireblocksSigner** ([rust/src/fireblocks/mod.rs](rust/src/fireblocks/mod.rs))
148+
- Uses Fireblocks API with EdDSA (Ed25519) signing
149+
- Requires `init()` to fetch public key before use
150+
- JWT-based authentication
151+
- Availability checked via user details endpoint
152+
153+
7. **GCP KMS** ([rust/src/gcp_kms/mod.rs](rust/src/gcp_kms/mod.rs))
154+
- Uses Google Cloud SDK with EdDSA (Ed25519) signing
155+
- PureEdDSA mode with `EC_SIGN_ED25519` algorithm
156+
- Automatic credential discovery via ADC
157+
- Availability checked via `GetCryptoKeyVersion`
158+
139159
### Error Handling
140160

141161
All errors are centralized in [rust/src/error.rs](rust/src/error.rs) using `thiserror`. The `SignerError` enum covers key formats, signing failures, remote API errors, serialization, and configuration issues.
@@ -147,21 +167,29 @@ The library uses Cargo features for zero-cost abstraction:
147167
- `vault` - Adds VaultSigner with reqwest, vaultrs, base64
148168
- `privy` - Adds PrivySigner with reqwest, base64
149169
- `turnkey` - Adds TurnkeySigner with reqwest, base64, p256, hex, chrono
170+
- `aws_kms` - Adds KmsSigner with aws-sdk-kms
171+
- `fireblocks` - Adds FireblocksSigner with reqwest, jsonwebtoken
172+
- `gcp_kms` - Adds GcpKmsSigner with google-cloud-kms-v1, google-cloud-auth
150173
- `all` - Enables all backends
151174

152175
At least one feature must be enabled (enforced by `compile_error!` in lib.rs).
153176

154177
## Testing
155178

156-
Tests are co-located with implementation code in each module. Remote signers (Vault, Privy, Turnkey) use `wiremock` to mock HTTP endpoints, avoiding actual API calls during testing. Tests cover:
179+
Tests are co-located with implementation code in each module. Remote signers (Vault, Privy, Turnkey, AWS, Fireblocks, GCP) use `wiremock` to mock HTTP endpoints, avoiding actual API calls during testing. Tests cover:
157180
- Constructor validation (invalid keys, etc.)
158181
- Successful signing operations
159182
- Error cases (unauthorized, malformed responses)
160183
- Availability checks
161184

162185
Run specific backend tests:
163186
```bash
187+
cd rust && cargo test --features vault vault::tests
164188
cd rust && cargo test --features privy privy::tests
189+
cd rust && cargo test --features turnkey turnkey::tests
190+
cd rust && cargo test --features aws_kms aws_kms::tests
191+
cd rust && cargo test --features fireblocks fireblocks::tests
192+
cd rust && cargo test --features gcp_kms gcp_kms::tests
165193
```
166194

167195
## Key Implementation Notes
@@ -170,5 +198,7 @@ cd rust && cargo test --features privy privy::tests
170198
- Privy and Turnkey use Base64 encoding for payloads/responses
171199
- Vault uses Base64 for both input and output
172200
- Turnkey requires special handling for signature component padding (see [rust/src/turnkey/mod.rs:125-136](rust/src/turnkey/mod.rs))
173-
- PrivySigner must call `init()` before use; other signers are ready after construction
201+
- PrivySigner and FireblocksSigner must call `init()` before use; other signers are ready after construction
202+
- AWS KMS and GCP KMS use official cloud SDKs with automatic credential discovery
203+
- GCP KMS operates in PureEdDSA mode with `EC_SIGN_ED25519` algorithm
174204
- The unified `Signer` enum uses conditional compilation extensively with `#[cfg(feature = "...")]`

rust/Cargo.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ fireblocks = [
2424
"dep:hex",
2525
"dep:chrono",
2626
]
27-
all = ["memory", "vault", "privy", "turnkey", "aws_kms", "fireblocks"]
27+
gcp_kms = ["dep:google-cloud-kms-v1", "dep:google-cloud-auth", "dep:reqwest"]
28+
all = ["memory", "vault", "privy", "turnkey", "aws_kms", "fireblocks", "gcp_kms"]
2829

2930
# SDK version selection (mutually exclusive)
3031
sdk-v2 = ["dep:solana-sdk"]
@@ -62,6 +63,8 @@ aws-config = { version = "1.1.7", optional = true }
6263
jsonwebtoken = { version = "10.2", optional = true, features = ["rust_crypto"] }
6364
sha2 = { version = "0.10.9", optional = true }
6465
uuid = { version = "1.19", optional = true, features = ["v4"] }
66+
google-cloud-kms-v1 = { version = "1.3.0", optional = true }
67+
google-cloud-auth = { version = "1.4.0", optional = true }
6568

6669
# Core dependencies (used by all signers for transaction serialization)
6770
bincode = "1.3"
@@ -74,3 +77,5 @@ rand = "0.8.0"
7477
dotenvy = "0.15.7"
7578
litesvm = "0.7.0"
7679
litesvm-v3 = { package = "litesvm", version = "0.8.1" }
80+
serial_test = "3.3.1"
81+
scoped-env = "2.1"

0 commit comments

Comments
 (0)