diff --git a/CLAUDE.md b/CLAUDE.md index b6c46891..055c5175 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -348,12 +348,47 @@ allowed_spl_paid_tokens = [ disallowed_accounts = [] # Blocked account addresses # Fee payer policy controls what actions the fee payer can perform -# All default to true for backward compatibility -[validation.fee_payer_policy] -allow_sol_transfers = true # Allow fee payer to be source in SOL transfers -allow_spl_transfers = true # Allow fee payer to be source in SPL token transfers -allow_token2022_transfers = true # Allow fee payer to be source in Token2022 transfers -allow_assign = true # Allow fee payer to use Assign instruction +# Organized by program type with 28 granular controls +# All default to false for security +[validation.fee_payer_policy.system] +allow_transfer = false # System Transfer/TransferWithSeed +allow_assign = false # System Assign/AssignWithSeed +allow_create_account = false # System CreateAccount/CreateAccountWithSeed +allow_allocate = false # System Allocate/AllocateWithSeed + +[validation.fee_payer_policy.system.nonce] +allow_initialize = false # InitializeNonceAccount +allow_advance = false # AdvanceNonceAccount +allow_authorize = false # AuthorizeNonceAccount +allow_withdraw = false # WithdrawNonceAccount + +[validation.fee_payer_policy.spl_token] +allow_transfer = false # Transfer/TransferChecked +allow_burn = false # Burn/BurnChecked +allow_close_account = false # CloseAccount +allow_approve = false # Approve/ApproveChecked +allow_revoke = false # Revoke +allow_set_authority = false # SetAuthority +allow_mint_to = false # MintTo/MintToChecked +allow_initialize_mint = false # InitializeMint/InitializeMint2 +allow_initialize_account = false # InitializeAccount/InitializeAccount3 +allow_initialize_multisig = false # InitializeMultisig/InitializeMultisig2 +allow_freeze_account = false # FreezeAccount +allow_thaw_account = false # ThawAccount + +[validation.fee_payer_policy.token_2022] +allow_transfer = false # Transfer/TransferChecked +allow_burn = false # Burn/BurnChecked +allow_close_account = false # CloseAccount +allow_approve = false # Approve/ApproveChecked +allow_revoke = false # Revoke +allow_set_authority = false # SetAuthority +allow_mint_to = false # MintTo/MintToChecked +allow_initialize_mint = false # InitializeMint/InitializeMint2 +allow_initialize_account = false # InitializeAccount/InitializeAccount3 +allow_initialize_multisig = false # InitializeMultisig/InitializeMultisig2 +allow_freeze_account = false # FreezeAccount +allow_thaw_account = false # ThawAccount ``` ### Environment Variables set in `signers.toml` @@ -367,29 +402,60 @@ RUST_LOG=debug # Logging level ### Overview -The fee payer policy system provides fine-grained control over what actions the fee payer can perform in transactions. By default, all actions are permitted to maintain backward compatibility with existing behavior. +The fee payer policy system provides granular control over what actions the fee payer can perform in transactions. The policy is organized by program type (System, SPL Token, Token-2022) and covers 28 different instruction types. By default, all actions are permitted to maintain backward compatibility with existing behavior. ### Policy Configuration -The fee payer policy is configured via the `[validation.fee_payer_policy]` section in `kora.toml`: +The fee payer policy is configured via nested sections in `kora.toml`: +- `[validation.fee_payer_policy.system]` - System program instructions (4 fields) +- `[validation.fee_payer_policy.system.nonce]` - Nonce account operations (4 fields) +- `[validation.fee_payer_policy.spl_token]` - SPL Token program instructions (12 fields) +- `[validation.fee_payer_policy.token_2022]` - Token-2022 program instructions (12 fields) ### Implementation Details **Core Structure** (`crates/lib/src/config.rs`): -- `FeePayerPolicy` struct with 4 boolean fields -- `Default` implementation sets all fields to `true` (permissive) +- `FeePayerPolicy` struct with nested policy structs for each program type +- `SystemInstructionPolicy` - Controls System program operations including nested `NonceInstructionPolicy` +- `SplTokenInstructionPolicy` - Controls SPL Token program operations +- `Token2022InstructionPolicy` - Controls Token-2022 program operations +- All `Default` implementations set fields to `true` (permissive) for backward compatibility - `#[serde(default)]` attribute ensures backward compatibility **Validation Logic** (`crates/lib/src/transaction/validator.rs`): - `TransactionValidator` stores the policy configuration -- `is_fee_payer_source()` method checks policy flags before validating restrictions +- Program-specific validation methods check policy flags before validating restrictions +- Uses macro-based validation patterns for consistent enforcement across instruction types - Different validation logic for each program type (System, SPL Token, Token2022) -**Supported Actions**: -1. **SOL Transfers** - System program Transfer and TransferWithSeed instructions -2. **SPL Token Transfers** - SPL Token program Transfer and TransferChecked instructions -3. **Token2022 Transfers** - Token2022 program Transfer and TransferChecked instructions -4. **Assign** - System program Assign instruction (changes account owner) +**Supported Actions by Program Type**: + +**System Program (8 controls)**: +1. **Transfer** - Transfer and TransferWithSeed instructions (fee payer as sender) +2. **Assign** - Assign and AssignWithSeed instructions (fee payer as authority) +3. **CreateAccount** - CreateAccount and CreateAccountWithSeed instructions (fee payer as funding source) +4. **Allocate** - Allocate and AllocateWithSeed instructions (fee payer as account owner) +5. **Nonce Initialize** - InitializeNonceAccount instruction (fee payer set as authority) +6. **Nonce Advance** - AdvanceNonceAccount instruction (fee payer as authority) +7. **Nonce Authorize** - AuthorizeNonceAccount instruction (fee payer as current authority) +8. **Nonce Withdraw** - WithdrawNonceAccount instruction (fee payer as authority) + +**SPL Token Program (12 controls)**: +1. **Transfer** - Transfer and TransferChecked instructions (fee payer as owner) +2. **Burn** - Burn and BurnChecked instructions (fee payer as owner) +3. **CloseAccount** - CloseAccount instruction (fee payer as owner) +4. **Approve** - Approve and ApproveChecked instructions (fee payer as owner) +5. **Revoke** - Revoke instruction (fee payer as owner) +6. **SetAuthority** - SetAuthority instruction (fee payer as current authority) +7. **MintTo** - MintTo and MintToChecked instructions (fee payer as mint authority) +8. **InitializeMint** - InitializeMint and InitializeMint2 instructions (fee payer as mint authority) +9. **InitializeAccount** - InitializeAccount and InitializeAccount3 instructions (fee payer as owner) +10. **InitializeMultisig** - InitializeMultisig and InitializeMultisig2 instructions (fee payer as signer) +11. **FreezeAccount** - FreezeAccount instruction (fee payer as freeze authority) +12. **ThawAccount** - ThawAccount instruction (fee payer as freeze authority) + +**Token-2022 Program (12 controls)**: +- Identical instruction set and controls as SPL Token Program ## Private Key Formats diff --git a/docs/contributors/ADDING_SIGNERS.md b/docs/contributors/ADDING_SIGNERS.md index 7eee6c1a..4ff67135 100644 --- a/docs/contributors/ADDING_SIGNERS.md +++ b/docs/contributors/ADDING_SIGNERS.md @@ -6,397 +6,250 @@ This guide is for wallet service providers who want to integrate their key manag ## Architecture Overview -Kora uses an enum-based architecture where all signers are wrapped in a unified `KoraSigner` enum. Your signer will be added as a new variant to this enum, allowing Kora to switch between different signing providers at runtime based on CLI flags. +Kora uses the external [`solana-signers`](https://github.com/solana-foundation/solana-signers) crate for all signing operations. This architecture provides a unified signing interface for signing Solana transactions. To add a new signer to Kora, you'll need to: + +1. **First**: Add your signer implementation to the `solana-signers` crate +2. **Second**: Add configuration support for your signer in Kora ## Step-by-Step Integration Guide ### Quick Integration Checklist -- [ ] Create your Signer Module - - Define types and configuration - - Implement `KoraSigner`'s core signing methods (`sign` and `sign_solana`) - - Add initialization logic based on your API's requirements -- [ ] Update the `KoraSigner` enum -- [ ] Update `SignerTypeConfig` enum in `config.rs` for multi-signer support -- [ ] Add initialization logic in `init.rs` -- [ ] Add CLI arguments +**Part 1: Add Signer to `solana-signers` Crate** +- [ ] Implement your signer following the [solana-signers integration guide](https://github.com/solana-foundation/solana-signers/blob/main/docs/ADDING_SIGNERS.md) +- [ ] Wait for PR approval and crate publication + +**Part 2: Add Kora Configuration Support** +- [ ] Update Cargo.toml's dependency so that `solana-signers` crate uses the latest version (that includes your signer) +- [ ] Add configuration struct for your signer's environment variables +- [ ] Update `SignerTypeConfig` enum in `crates/lib/src/signer/config.rs` +- [ ] Add validation logic for your signer's config +- [ ] Add build logic to construct your signer from config +- [ ] Export configuration struct in `crates/lib/src/signer/mod.rs` +- [ ] (Optional) Add test mock builder in `crates/lib/src/tests/config_mock.rs` +- [ ] Update example configuration files - [ ] Update test scripts to include your signer (see below) - [ ] Update documentation to include your signer (see below) -- [ ] Submit PR +- [ ] Submit PR to Kora repository -### Step 1: Create Your Signer Module +### Add Signer Support in Kora -Create a new directory under `crates/lib/src/signer/` for your implementation: +First, ensure your signer is supported in the `solana-signers` crate. If it is not, follow the guide at: +**[https://github.com/solana-foundation/solana-signers/blob/main/docs/ADDING_SIGNERS.md](https://github.com/solana-foundation/solana-signers/blob/main/docs/ADDING_SIGNERS.md)** -```bash -crates/lib/src/signer/ -├── your_service/ -│ ├── mod.rs # Module exports -│ ├── signer.rs # Main implementation -│ ├── config.rs # Configuration -│ └── types.rs # Types and configuration -``` +#### Step 1: Update Cargo.toml -Export each of the files in `mod.rs`: +Update Cargo.toml's dependency so that `solana-signers` crate uses the latest version (that includes your signer): -```rust -pub mod config; -pub mod signer; -pub mod types; +```toml +[dependencies] +solana-signers = { version = "X.Y.Z", default-features = false, features = [ + "all", + "sdk-v3", +] } ``` -### Step 2: Define Your Types +#### Step 2: Define Your Configuration Struct -In `your_service/types.rs`, define your signer struct and any necessary types (e.g. error types, config, API request/response types, etc.): +In `crates/lib/src/signer/config.rs`, add a new configuration struct for your signer that defines which environment variables are needed. For example: ```rust -use reqwest::Client; -use serde::{Deserialize, Serialize}; -use solana_sdk::pubkey::Pubkey; - -#[derive(Clone, Debug)] -pub struct YourServiceSigner { - // Your API credentials - pub api_key: String, - pub api_secret: String, - pub wallet_id: String, - - // HTTP client for API calls - pub client: Client, - - // Cache the public key - pub public_key: Pubkey, - - // Your API base URL - pub api_base_url: String, -} - -// Error types for your signer -#[derive(Debug)] -pub enum YourServiceError { - MissingConfig(&'static str), - ApiError(u16), - InvalidSignature, - RateLimitExceeded, - // Add more as needed -} - -// Implement Display and Error traits -impl std::fmt::Display for YourServiceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - // Implementation - } -} - -impl std::error::Error for YourServiceError {} - -// API request/response types -#[derive(Serialize)] -pub struct SignTransactionRequest { - pub method: &'static str, - pub params: SignTransactionParams, -} - -#[derive(Deserialize, Debug)] -pub struct SignTransactionResponse { - pub method: String, - pub data: SignTransactionData, +/// YourService signer configuration +#[derive(Clone, Serialize, Deserialize)] +pub struct YourServiceSignerConfig { + pub api_key_env: String, + pub api_secret_env: String, + pub wallet_id_env: String, } ``` -Consider implementing your error types as a `KoraError` variant in `crates/lib/src/error.rs`. - -### Step 3: Implement the Signer Methods - -In `your_service/signer.rs`, implement the core signing logic. Some methods you should implement are: +#### Step 2: Add Your Signer to `SignerTypeConfig` Enum -- `new`: Create a new instance of your signer -- `solana_pubkey`: Get the Solana public key for this signer (as a Solana `Pubkey`) -- `sign`: Sign a `VersionedTransaction` and return `Vec` (raw bytes) -- `sign_solana`: Sign a `VersionedTransaction` and return a Solana `Signature` -- other methods core to your signer's implementation (e.g., `init`, `call_signing_api`, etc.) +Add your signer variant to the `SignerTypeConfig` enum in `crates/lib/src/signer/config.rs`: ```rust -use crate::signer::Signature as KoraSignature; -use solana_sdk::{ - signature::Signature, - transaction::VersionedTransaction, -}; +/// Signer type-specific configuration +#[derive(Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum SignerTypeConfig { + // Existing signer variants + Memory { #[serde(flatten)] config: MemorySignerConfig }, + // ... existing variants ... -impl YourServiceSigner { - /// Create a new instance of your signer - pub fn new( - api_key: String, - api_secret: String, - wallet_id: String, - ) -> Result { - Ok(Self { - api_key, - api_secret, - wallet_id, - client: reqwest::Client::new(), - public_key: Pubkey::default(), // Will be initialized later - api_base_url: "https://api.yourservice.com/v1".to_string(), - }) - } - - /// Get the Solana public key for this signer - pub fn solana_pubkey(&self) -> Pubkey { - self.public_key - } - - /// Sign a transaction and return raw bytes - pub async fn sign( - &self, - transaction: &VersionedTransaction, - ) -> Result, YourServiceError> { - // 1. Serialize the transaction message - let message_bytes = transaction.message.serialize(); - - // 2. Call your API to sign - let signature = self.call_signing_api(&message_bytes).await?; - - // 3. Return the signature bytes - Ok(signature) - } - - /// Sign and return a Solana signature - pub async fn sign_solana( - &self, - transaction: &VersionedTransaction, - ) -> Result { - let sig_bytes = self.sign(transaction).await?; - - // Convert to Solana signature (must be exactly 64 bytes) - let sig_array: [u8; 64] = sig_bytes - .try_into() - .map_err(|_| YourServiceError::InvalidSignature)?; - - Ok(Signature::from(sig_array)) - } - - // Private helper methods - async fn call_signing_api( - &self, - message: &[u8], - ) -> Result, YourServiceError> { - // Implementation specific to your API - } + // Add your signer here + YourService { + #[serde(flatten)] + config: YourServiceSignerConfig, + }, } ``` -### Step 4: Update the KoraSigner Enum +#### Step 3: Add Build Logic -Add your signer to the `KoraSigner` enum in `crates/lib/src/signer/signer.rs`: +In the same file (`config.rs`), add a method to build your signer from configuration in the `SignerConfig` implementation: ```rust -#[derive(Clone)] -pub enum KoraSigner { - Memory(SolanaMemorySigner), - Turnkey(TurnkeySigner), - Vault(VaultSigner), - Privy(PrivySigner), - YourService(YourServiceSigner), // Add your signer here -} - -// Update the trait implementation -impl KoraSigner { - pub fn solana_pubkey(&self) -> Pubkey { - match self { - // ... existing implementations - KoraSigner::YourService(signer) => signer.solana_pubkey(), - } - } -} +impl SignerConfig { + pub async fn build_signer_from_config(config: &SignerConfig) -> Result { + match &config.config { + // ... existing cases -impl super::Signer for KoraSigner { - type Error = KoraError; - - async fn sign( - &self, - transaction: &VersionedTransaction, - ) -> Result { - match self { - // ... existing implementations - KoraSigner::YourService(signer) => { - let sig = signer.sign(transaction).await?; - Ok(super::Signature { - bytes: sig, - is_partial: false, - }) + SignerTypeConfig::YourService { config: your_service_config } => { + Self::build_your_service_signer(your_service_config, &config.name).await } } } - - async fn sign_solana( - &self, - transaction: &VersionedTransaction, - ) -> Result { - match self { - // ... existing implementations - KoraSigner::YourService(signer) => { - signer.sign_solana(transaction) - .await - .map_err(KoraError::from) - } - } + + // Add this new method + async fn build_your_service_signer( + config: &YourServiceSignerConfig, + signer_name: &str, + ) -> Result { + // Update the environment variable names to match your signer's configuration + let api_key = get_env_var_for_signer(&config.api_key_env, signer_name)?; + let api_secret = get_env_var_for_signer(&config.api_secret_env, signer_name)?; + let wallet_id = get_env_var_for_signer(&config.wallet_id_env, signer_name)?; + + // Call the constructor from solana-signers crate + Signer::from_your_service(api_key, api_secret, wallet_id) + .await + .map_err(|e| { + KoraError::SigningError(format!( + "Failed to create YourService signer '{signer_name}': {}", + sanitize_error!(e) + )) + }) } } ``` -### Step 5: Update the SignerTypeConfig Enum - -Add your signer to the `SignerTypeConfig` enum in `crates/lib/src/signer/config.rs`: +**Note**: The method name `Signer::from_your_service()` should match what you implemented in the `solana-signers` crate. -```rust -/// Signer type-specific configuration with environment variable references -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "snake_case")] -pub enum SignerTypeConfig { - // ... existing variants - - /// YourService signer configuration - YourService { - /// Environment variable for YourService API key - api_key_env: String, - /// Environment variable for YourService API secret - api_secret_env: String, - /// Environment variable for YourService wallet ID - wallet_id_env: String, - }, -} -``` +#### Step 4: Add Validation Logic -Also update the `build_signer_from_config` method in the same file: +Add validation for your signer's configuration in the `validate_individual_signer_config` method: ```rust impl SignerConfig { - pub async fn build_signer_from_config(config: &SignerConfig) -> Result { - match &config.config { + pub fn validate_individual_signer_config(&self, index: usize) -> Result<(), KoraError> { + // ... existing validation + + match &self.config { // ... existing cases - - SignerTypeConfig::YourService { api_key_env, api_secret_env, wallet_id_env } => { - let api_key = get_env_var_for_signer(api_key_env, &config.name)?; - let api_secret = get_env_var_for_signer(api_secret_env, &config.name)?; - let wallet_id = get_env_var_for_signer(wallet_id_env, &config.name)?; - - let signer = YourServiceSigner::new(api_key, api_secret, wallet_id) - .map_err(|e| { - KoraError::ValidationError(format!( - "Failed to create YourService signer '{}': {}", - config.name, e - )) - })?; - - Ok(KoraSigner::YourService(signer)) + + SignerTypeConfig::YourService { config } => { + Self::validate_your_service_config(config, &self.name) } } } -} -``` -And update the validation method: - -```rust -fn validate_individual_signer_config(&self, index: usize) -> Result<(), KoraError> { - match &self.config { - // ... existing cases - - SignerTypeConfig::YourService { api_key_env, api_secret_env, wallet_id_env } => { - let env_vars = [ - ("api_key_env", api_key_env), - ("api_secret_env", api_secret_env), - ("wallet_id_env", wallet_id_env), - ]; - for (field_name, env_var) in env_vars { - if env_var.is_empty() { - return Err(KoraError::ValidationError(format!( - "YourService signer '{}' must specify non-empty {}", - self.name, field_name - ))); - } + // Add this new validation method + fn validate_your_service_config( + config: &YourServiceSignerConfig, + signer_name: &str, + ) -> Result<(), KoraError> { + // Update the environment variable names to match your signer's configuration + let env_vars = [ + ("api_key_env", &config.api_key_env), + ("api_secret_env", &config.api_secret_env), + ("wallet_id_env", &config.wallet_id_env), + ]; + + for (field_name, env_var) in env_vars { + if env_var.is_empty() { + return Err(KoraError::ValidationError(format!( + "YourService signer '{signer_name}' must specify non-empty {field_name}" + ))); } } + Ok(()) } - Ok(()) } ``` -### Step 6: Add Initialization Logic +#### Step 5: Export Your Configuration -Update `crates/lib/src/signer/init.rs` to include your signer: +Add your new config struct to the module exports in `crates/lib/src/signer/mod.rs` (or at the top of the file if it's public): -- add `init_your_service_signer` to define your service's signer as a `KoraSigner` -- add your service to the `init_signer_type` function if specified in the CLI args (we'll be adding these in the next step) +```rust +pub use config::{ + MemorySignerConfig, + PrivySignerConfig, + SignerTypeConfig, + TurnkeySignerConfig, + VaultSignerConfig, + YourServiceSignerConfig, // Add this + // ... other exports +}; +``` + + +## Testing Your Integration + +### Add Test Mock Builder + +To make testing easier, add a builder method to `SignerPoolConfigBuilder` in `crates/lib/src/tests/config_mock.rs`: ```rust -// Add your args struct import -use crate::rpc_server::args::YourServiceArgs; - -pub fn init_signer_type(args: &RpcArgs) -> Result { - if args.turnkey_args.turnkey_signer { - init_turnkey_signer(&args.turnkey_args) - } else if args.vault_args.vault_signer { - init_vault_signer(&args.vault_args) - } else if args.privy_args.privy_signer { - init_privy_signer(&args.privy_args) - } else if args.your_service_args.your_service_signer { - init_your_service_signer(&args.your_service_args) - } else { - init_memory_signer(args.private_key.as_ref()) - } -} +impl SignerPoolConfigBuilder { + // ... existing methods -fn init_your_service_signer(config: &YourServiceArgs) -> Result { - // Extract required configuration - let api_key = config - .your_service_api_key - .clone() - .or_else(|| std::env::var("YOUR_SERVICE_API_KEY").ok()) - .ok_or_else(|| KoraError::SigningError("YourService API key required".to_string()))?; - - let api_secret = config - .your_service_api_secret - .clone() - .or_else(|| std::env::var("YOUR_SERVICE_API_SECRET").ok()) - .ok_or_else(|| KoraError::SigningError("YourService API secret required".to_string()))?; - - let wallet_id = config - .your_service_wallet_id - .clone() - .or_else(|| std::env::var("YOUR_SERVICE_WALLET_ID").ok()) - .ok_or_else(|| KoraError::SigningError("YourService wallet ID required".to_string()))?; - - // Create the signer - let mut signer = YourServiceSigner::new(api_key, api_secret, wallet_id)?; - - // Initialize if needed (fetch public key, etc) - // Note: This would need to be handled async in practice - - Ok(KoraSigner::YourService(signer)) + pub fn with_your_service_signer( + mut self, + name: String, + api_key_env: String, + api_secret_env: String, + wallet_id_env: String, + weight: Option, + ) -> Self { + let signer = SignerConfig { + name, + weight, + config: SignerTypeConfig::YourService { + config: YourServiceSignerConfig { + api_key_env, + api_secret_env, + wallet_id_env, + }, + }, + }; + self.config.signers.push(signer); + self + } } ``` -### Step 7: Export Your Module - -Update `crates/lib/src/signer/mod.rs`: +This allows other tests to easily create mock configurations that include your signer: ```rust -pub mod your_service; -// ... other modules - -pub use your_service::types::YourServiceSigner; +use crate::tests::config_mock::SignerPoolConfigBuilder; + +let config = SignerPoolConfigBuilder::new() + .with_your_service_signer( + "yourservice_test".to_string(), + "YOUR_SERVICE_API_KEY".to_string(), + "YOUR_SERVICE_API_SECRET".to_string(), + "YOUR_SERVICE_WALLET_ID".to_string(), + Some(1) + ) + .build(); ``` -## Testing Your Integration ### Environment Variables -Define a `example-signer.toml` with your signer's configuration and necessary environment variables defined. Add the example environment variables to the following files: `.env.example` and `.env` in the root of the project, and in `./sdks/ts/`: +Add the example environment variables to the following files: +- `.env.example` (root of the project) +- `.env` (root of the project, for local testing) +- `./sdks/ts/.env.example` +- `./sdks/ts/.env` -- `YOUR_SERVICE_API_KEY`: The API key for your service. -- `YOUR_SERVICE_API_SECRET`: The API secret for your service. -- `YOUR_SERVICE_WALLET_ID`: The wallet ID for your service. +```bash +# YourService Signer Configuration +YOUR_SERVICE_API_KEY=your_api_key_here +YOUR_SERVICE_API_SECRET=your_api_secret_here +YOUR_SERVICE_WALLET_ID=your_wallet_id_here +``` ### Integration Tests @@ -408,6 +261,9 @@ Create a new signer configuration file in `tests/src/common/fixtures/` for your ```toml # tests/src/common/fixtures/signers-your-service.toml +[signer_pool] +strategy = "round_robin" + [[signers]] name = "yourservice_main" type = "your_service" @@ -429,19 +285,7 @@ port = "8090" # Use a unique port tests = ["your_service"] ``` -#### 3. TypeScript SDK Integration - -For TypeScript SDK testing with your signer: - -1. Update `sdks/ts/test/setup.ts` to recognize your signer type: - - Add environment variable handling for `KORA_SIGNER_TYPE=your-service` - -2. Add a test script in `sdks/ts/package.json`: - ```json - "test:integration:your-service": "KORA_SIGNER_TYPE=your-service pnpm test integration.test.ts" - ``` - -#### 4. Running Tests +#### 3. Running Tests Make sure your environment is set up: @@ -502,63 +346,71 @@ YOUR_SERVICE_API_SECRET="your_api_secret" YOUR_SERVICE_WALLET_ID="your_wallet_id" \``` -### Configure Signer.toml +### Configure signers.toml + +\```toml +[signer_pool] +strategy = "round_robin" -\```bash [[signers]] name = "yourservice_main" type = "your_service" api_key_env = "YOUR_SERVICE_API_KEY" api_secret_env = "YOUR_SERVICE_API_SECRET" wallet_id_env = "YOUR_SERVICE_WALLET_ID" +weight = 1 \``` +### Run Kora with YourService Signer + +\```bash +kora rpc start --signers-config signers.toml +\``` +``` ### 2. Update README Add your service to the main README's signer list. -### 3. Add Example Configuration - -Create an example `.env` configuration: - -```bash -# YourService Signer Configuration -YOUR_SERVICE_API_KEY=your_api_key_here -YOUR_SERVICE_API_SECRET=your_api_secret_here -YOUR_SERVICE_WALLET_ID=your_wallet_id_here -``` - ## Submission Checklist -Before submitting your PR: - +- [ ] Your signer is supported in the `solana-signers` crate +- [ ] Updated `solana-signers` dependency in Cargo.toml to latest version +- [ ] Added configuration struct for your signer +- [ ] Added `SignerTypeConfig` variant +- [ ] Added build logic in `build_signer_from_config` +- [ ] Added validation logic in `validate_individual_signer_config` +- [ ] Exported configuration struct in `mod.rs` +- [ ] (Optional) Added test mock builder method in `config_mock.rs` - [ ] Code compiles without warnings -- [ ] All tests pass -- [ ] Documentation is complete -- [ ] Example configuration (.env.example) provided +- [ ] All tests pass (`make test` and `make test-integration`) +- [ ] Documentation added to [`docs/operators/SIGNERS.md`](/docs/operators/SIGNERS.md) +- [ ] Example configuration files created (`.toml` and `.env.example`) - [ ] No hardcoded values or secrets - [ ] Error messages are helpful - [ ] Follows Rust naming conventions (snake_case) -- [ ] Linting passes (`make lint` and `make format-ts-sdk`) +- [ ] Linting passes (`make lint`) - [ ] Contact the Kora team with API Keys for integration testing ## Getting Help -- Open an issue for design discussions +- **For signer implementation**: Open an issue in the [`solana-signers`](https://github.com/solana-foundation/solana-signers) repository +- **For Kora integration**: Open an issue in the Kora repository for design discussions - Join our community channels -- Review existing signer implementations for patterns in [`crates/lib/src/signer/`](/crates/lib/src/signer/) +- Review existing signer configurations in [`crates/lib/src/signer/config.rs`](/crates/lib/src/signer/config.rs) ## Example PR Structure -```sh -feat(signer): add YourService signer integration - -- Implement Signer trait for YourService -- Add CLI arguments and initialization -- Add signer to Signers Guide -- Add integration tests script to Makefile - +**For Kora repository:** +``` +feat(signer): add YourService signer configuration support + +- Add YourServiceSignerConfig struct +- Add YourService variant to SignerTypeConfig enum +- Add build and validation logic for YourService +- Add example configuration files +- Add documentation to SIGNERS.md +- Add integration tests ``` Welcome to the Kora ecosystem! We're excited to have your key management solution as part of the platform. diff --git a/docs/operators/CONFIGURATION.md b/docs/operators/CONFIGURATION.md index 86a058d4..6317e6a6 100644 --- a/docs/operators/CONFIGURATION.md +++ b/docs/operators/CONFIGURATION.md @@ -277,34 +277,96 @@ blocked_account_extensions = [ ## Fee Payer Policy -The `[validation.fee_payer_policy]` section restricts what your Kora node's fee payer wallet can do. This is important to prevent unexpected behavior from users' transactions utilizing your Kora node as a signer. For example, if `allow_spl_transfers` is set to `false`, the Kora node would not sign transactions that include an SPL token transfer where the Kora node's fee payer is the instruction's authority. +The `[validation.fee_payer_policy]` section provides granular control over what actions your Kora node's fee payer wallet can perform. The policy is organized by program type (System, SPL Token, Token-2022) and covers all different instruction types. This prevents unexpected behavior from users' transactions utilizing your Kora node as a signer. +For example, if `spl_token.allow_transfer` is set to `false`, the Kora node will not sign transactions that include an SPL token transfer where the Kora node's fee payer is the transfer authority. ```toml [validation.fee_payer_policy] -allow_sol_transfers = false -allow_spl_transfers = false -allow_token2022_transfers = false -allow_assign = false -allow_burn = false -allow_close_account = false -allow_approve = false -``` -| Option | Description | Required | Type | -|--------|-------------|---------|---------| -| `allow_sol_transfers` | Allow SOL transfers where the Kora node's fee payer is the signer/authority | ✅ | boolean | -| `allow_spl_transfers` | Allow SPL token transfers where the Kora node's fee payer is the signer/authority | ✅ | boolean | -| `allow_token2022_transfers` | Allow Token-2022 transfers where the Kora node's fee payer is the signer/authority | ✅ | boolean | -| `allow_assign` | Allow account ownership changes where the Kora node's fee payer is the signer/authority | ✅ | boolean | -| `allow_burn` | Allow token burn operations where the Kora node's fee payer is the signer/authority | ✅ | boolean | -| `allow_close_account` | Allow closing token accounts where the Kora node's fee payer is the signer/authority | ✅ | boolean | -| `allow_approve` | Allow token delegation/approval where the Kora node's fee payer is the signer/authority | ✅ | boolean | +[validation.fee_payer_policy.system] +allow_transfer = false # System Transfer/TransferWithSeed +allow_assign = false # System Assign/AssignWithSeed +allow_create_account = false # System CreateAccount/CreateAccountWithSeed +allow_allocate = false # System Allocate/AllocateWithSeed + +[validation.fee_payer_policy.system.nonce] +allow_initialize = false # InitializeNonceAccount +allow_advance = false # AdvanceNonceAccount +allow_authorize = false # AuthorizeNonceAccount +allow_withdraw = false # WithdrawNonceAccount + +[validation.fee_payer_policy.spl_token] +allow_transfer = false # Transfer/TransferChecked +allow_burn = false # Burn/BurnChecked +allow_close_account = false # CloseAccount +allow_approve = false # Approve/ApproveChecked +allow_revoke = false # Revoke +allow_set_authority = false # SetAuthority +allow_mint_to = false # MintTo/MintToChecked +allow_initialize_mint = false # InitializeMint/InitializeMint2 +allow_initialize_account = false # InitializeAccount/InitializeAccount3 +allow_initialize_multisig = false # InitializeMultisig/InitializeMultisig2 +allow_freeze_account = false # FreezeAccount +allow_thaw_account = false # ThawAccount + +[validation.fee_payer_policy.token_2022] +allow_transfer = false # Transfer/TransferChecked +allow_burn = false # Burn/BurnChecked +allow_close_account = false # CloseAccount +allow_approve = false # Approve/ApproveChecked +allow_revoke = false # Revoke +allow_set_authority = false # SetAuthority +allow_mint_to = false # MintTo/MintToChecked +allow_initialize_mint = false # InitializeMint/InitializeMint2 +allow_initialize_account = false # InitializeAccount/InitializeAccount3 +allow_initialize_multisig = false # InitializeMultisig/InitializeMultisig2 +allow_freeze_account = false # FreezeAccount +allow_thaw_account = false # ThawAccount +``` +### System Program Instructions + +| Option | Description | Default | Type | +|--------|-------------|---------|------| +| `allow_transfer` | Allow fee payer as sender in Transfer/TransferWithSeed instructions | `false` | boolean | +| `allow_assign` | Allow fee payer as authority in Assign/AssignWithSeed instructions | `false` | boolean | +| `allow_create_account` | Allow fee payer as funding payer in CreateAccount/CreateAccountWithSeed instructions | `false` | boolean | +| `allow_allocate` | Allow fee payer as account owner in Allocate/AllocateWithSeed instructions | `false` | boolean | +| `nonce.allow_initialize` | Allow fee payer to be set as nonce authority in InitializeNonceAccount | `false` | boolean | +| `nonce.allow_advance` | Allow fee payer as authority in AdvanceNonceAccount | `false` | boolean | +| `nonce.allow_authorize` | Allow fee payer as current authority in AuthorizeNonceAccount | `false` | boolean | +| `nonce.allow_withdraw` | Allow fee payer as authority in WithdrawNonceAccount | `false` | boolean | + +### SPL Token Program Instructions + +| Option | Description | Default | Type | +|--------|-------------|---------|------| +| `allow_transfer` | Allow fee payer as owner in Transfer/TransferChecked instructions | `false` | boolean | +| `allow_burn` | Allow fee payer as owner in Burn/BurnChecked instructions | `false` | boolean | +| `allow_close_account` | Allow fee payer as owner in CloseAccount instructions | `false` | boolean | +| `allow_approve` | Allow fee payer as owner in Approve/ApproveChecked instructions | `false` | boolean | +| `allow_revoke` | Allow fee payer as owner in Revoke instructions | `false` | boolean | +| `allow_set_authority` | Allow fee payer as current authority in SetAuthority instructions | `false` | boolean | +| `allow_mint_to` | Allow fee payer as mint authority in MintTo/MintToChecked instructions | `false` | boolean | +| `allow_initialize_mint` | Allow fee payer as mint authority in InitializeMint/InitializeMint2 instructions | `false` | boolean | +| `allow_initialize_account` | Allow fee payer as owner in InitializeAccount/InitializeAccount3 instructions | `false` | boolean | +| `allow_initialize_multisig` | Allow fee payer as signer in InitializeMultisig/InitializeMultisig2 instructions | `false` | boolean | +| `allow_freeze_account` | Allow fee payer as freeze authority in FreezeAccount instructions | `false` | boolean | +| `allow_thaw_account` | Allow fee payer as freeze authority in ThawAccount instructions | `false` | boolean | + +Token-2022 supports the same instruction set as SPL Token with identical configuration options (under the `[validation.fee_payer_policy.token_2022]` section). ### Security Considerations -**SECURITY WARNING:** For security reasons, it is recommended to set all of these to `false` and only enable as needed. This will prevent unwanted behavior such as users draining your fee payer account or burning tokens from your fee payer account. +**SECURITY WARNING:** For security reasons, it is recommended to set all of these to `false` (default) and only enable as needed. This will prevent unwanted behavior such as users draining your fee payer account or burning tokens from your fee payer account. This prevents: + +- **Account Drainage**: Users transferring SOL or tokens from your fee payer account +- **Authority Takeover**: Users changing authorities on accounts owned by your fee payer +- **Unauthorized Minting**: Users minting tokens if your fee payer has mint authority +- **Account Manipulation**: Users freezing, closing, or modifying accounts controlled by your fee payer + +**Best Practice:** Start with all permissions disabled and enable only the minimum set needed for your specific use case. ## Price Configuration (optional) @@ -354,17 +416,22 @@ type = "free" #### Security Measures When Using Fixed/Free Pricing -1. **Disable Transfer Operations** - Prevent fee payer from being used as source in transfers: +1. **Disable All Transfer and Monetary Operations** - Prevent fee payer from being used as source in transfers: ```toml [validation.fee_payer_policy.system] - allow_transfer = false # Critical: Block SOL transfers - allow_create_account = false # Block account creation + allow_transfer = false # Block SOL transfers + allow_create_account = false # Block account creation with lamports + allow_allocate = false # Block space allocation - [validation.fee_payer_policy.spl_token] - allow_transfer = false # Block SPL transfers + [validation.fee_payer_policy.system.nonce] + allow_withdraw = false # Block nonce account withdrawals - [validation.fee_payer_policy.token_2022] - allow_transfer = false # Block Token2022 transfers + [validation.fee_payer_policy.spl_token] # and for [validation.fee_payer_policy.token_2022] + allow_transfer = false # Block SPL transfers + allow_burn = false # Block SPL token burning + allow_close_account = false # Block SPL token account closures (returns rent) + allow_mint_to = false # Block unauthorized SPL token minting + allow_initialize_account = false # Block account initialization ``` 2. **Enable Authentication** - Use authentication to prevent abuse: @@ -381,6 +448,12 @@ type = "free" max_allowed_lamports = 1000000 # 0.001 SOL maximum ``` +> **WARNING:** Particularly dangerous operations when using fixed/free pricing: +> - `allow_mint_to`: Could allow unlimited token creation if fee payer has mint authority +> - `allow_set_authority`: Could transfer control of critical accounts to attackers +> - `allow_transfer`: Enables direct drainage of fee payer token balances +> - `allow_close_account`: Returns rent to attacker-controlled accounts + ## Performance Monitoring (optional) The `[metrics]` section configures metrics collection and monitoring. This section is optional and by default, metrics are disabled. @@ -503,15 +576,46 @@ disallowed_accounts = [ "BadActor1111111111111111111111111111111111111111", ] -# Restrictive fee payer policy -[validation.fee_payer_policy] -allow_sol_transfers = false -allow_spl_transfers = false -allow_token2022_transfers = false -allow_assign = false -allow_burn = false -allow_close_account = false -allow_approve = false +# Restrictive fee payer policy (recommended for production) +[validation.fee_payer_policy.system] +allow_transfer = false # Block SOL transfers from fee payer +allow_assign = false # Block account ownership changes +allow_create_account = false # Block creating accounts with fee payer funds +allow_allocate = false # Block allocating space for fee payer accounts + +[validation.fee_payer_policy.system.nonce] +allow_initialize = false # Block nonce account initialization +allow_advance = false # Block nonce advancement +allow_authorize = false # Block nonce authority changes +allow_withdraw = false # Block nonce withdrawals + +[validation.fee_payer_policy.spl_token] +allow_transfer = false # Critical: Block SPL transfers +allow_burn = false # Block token burning +allow_close_account = false # Block account closures +allow_approve = false # Block token approvals +allow_revoke = false # Block delegate revocations +allow_set_authority = false # Block authority changes +allow_mint_to = false # Block minting operations +allow_initialize_mint = false # Block mint initialization +allow_initialize_account = false # Block account initialization +allow_initialize_multisig = false # Block multisig initialization +allow_freeze_account = false # Block account freezing +allow_thaw_account = false # Block account thawing + +[validation.fee_payer_policy.token_2022] +allow_transfer = false # Critical: Block Token2022 transfers +allow_burn = false # Block token burning +allow_close_account = false # Block account closures +allow_approve = false # Block token approvals +allow_revoke = false # Block delegate revocations +allow_set_authority = false # Block authority changes +allow_mint_to = false # Block minting operations +allow_initialize_mint = false # Block mint initialization +allow_initialize_account = false # Block account initialization +allow_initialize_multisig = false # Block multisig initialization +allow_freeze_account = false # Block account freezing +allow_thaw_account = false # Block account thawing # Token-2022 extension blocking [validation.token2022] diff --git a/docs/operators/FEES.md b/docs/operators/FEES.md index 846c3a57..52d16422 100644 --- a/docs/operators/FEES.md +++ b/docs/operators/FEES.md @@ -59,30 +59,35 @@ Kora supports three pricing models that determine how users are charged for tran ## ⚠️ Security Warning: Fixed/Free Pricing Models -**CRITICAL:** The fixed/free pricing models do **NOT** include fee payer outflow in the charged amount. This creates a significant security risk if not properly configured: If your fee payer policy allows transfers or other outflow operations, attackers can exploit this to drain your fee payer account: +**CRITICAL:** The fixed/free pricing models do **NOT** include fee payer outflow in the charged amount. This creates a significant security risk if not properly configured. If your fee payer policy allows transfers or other outflow operations, attackers can exploit this to drain your fee payer account. ### Required Security Controls -When using fixed/free pricing, you **MUST** configure restrictive fee payer policies: +When using fixed/free pricing, you **MUST** configure restrictive fee payer policies to block ALL monetary and authority-changing operations: ```toml [validation.fee_payer_policy.system] allow_transfer = false # Block SOL transfers allow_create_account = false # Block account creation with lamports +allow_allocate = false # Block space allocation -[validation.fee_payer_policy.spl_token] -allow_transfer = false # Block SPL token transfers +[validation.fee_payer_policy.system.nonce] +allow_withdraw = false # Block nonce account withdrawals -[validation.fee_payer_policy.token_2022] -allow_transfer = false # Block Token2022 transfers +[validation.fee_payer_policy.spl_token] # and for [validation.fee_payer_policy.token_2022] +allow_transfer = false # Block SPL transfers +allow_burn = false # Block SPL token burning +allow_close_account = false # Block SPL token account closures (returns rent) +allow_mint_to = false # Block unauthorized SPL token minting +allow_initialize_account = false # Block account initialization ``` ### Additional Protections -1. **Enable Authentication:** Always require API key or HMAC authentication with fixed pricing +1. **Enable Authentication:** Always require API key or HMAC authentication with fixed/free pricing 2. **Set Low Limits:** Use conservative `max_allowed_lamports` values 3. **Monitor Usage:** Track unusual patterns of high-outflow transactions -4. **Consider Margin Pricing:** Margin pricing automatically includes outflow costs +4. **Consider Margin Pricing:** Margin pricing automatically includes outflow costs in fees ### Validation Warnings @@ -94,7 +99,7 @@ kora --config kora.toml config validate Expected warnings for vulnerable configs: ``` -⚠️ SECURITY: Fixed pricing with allow_transfer=true for System instructions. +⚠️ SECURITY: Fixed pricing with system.allow_transfer=true. Users can make the fee payer transfer arbitrary SOL amounts at fixed cost. This can drain your fee payer account. ``` diff --git a/docs/x402/demo/X402_DEMO_GUIDE.md b/docs/x402/demo/X402_DEMO_GUIDE.md index 19647fe6..80e27990 100644 --- a/docs/x402/demo/X402_DEMO_GUIDE.md +++ b/docs/x402/demo/X402_DEMO_GUIDE.md @@ -230,12 +230,48 @@ allowed_tokens = [ api_key = "kora_facilitator_api_key_example" ``` -3. **Fee Payer Policy**: Configured to restrict signing unwanted transactions: +3. **Fee Payer Policy**: Configured to restrict signing unwanted transactions using granular controls: ```toml -[validation.fee_payer_policy] -allow_sol_transfers = false -# all other settings are false +[validation.fee_payer_policy.system] +allow_transfer = false +allow_assign = false +allow_create_account = false +allow_allocate = false + +[validation.fee_payer_policy.system.nonce] +allow_initialize = false +allow_advance = false +allow_authorize = false +allow_withdraw = false + +[validation.fee_payer_policy.spl_token] +allow_transfer = false +allow_burn = false +allow_close_account = false +allow_approve = false +allow_revoke = false +allow_set_authority = false +allow_mint_to = false +allow_initialize_mint = false +allow_initialize_account = false +allow_initialize_multisig = false +allow_freeze_account = false +allow_thaw_account = false + +[validation.fee_payer_policy.token_2022] +allow_transfer = false +allow_burn = false +allow_close_account = false +allow_approve = false +allow_revoke = false +allow_set_authority = false +allow_mint_to = false +allow_initialize_mint = false +allow_initialize_account = false +allow_initialize_multisig = false +allow_freeze_account = false +allow_thaw_account = false ``` 4. **Allowed Programs**: Ensure the system program, token program, associated token program, and compute budget program are in the allowlist: