설치
필요한 백엔드만 포함하여 solana-keychain를 Cargo.toml에 추가하세요:
[dependencies]# Memory signer only (default, Solana SDK 2.x)solana-keychain = "2.0"# Or specify backends explicitly (pick exactly one SDK feature)solana-keychain = { version = "2.0", default-features = false, features = ["vault", "aws_kms", "sdk-v2"] }# All remote backends on the Solana 4.x SDK linesolana-keychain = { version = "2.0", default-features = false, features = ["all", "sdk-v4"] }
1.x에서 업그레이드하시나요? 2.0 마이그레이션 가이드를 참고하세요.
기능 플래그
| 기능 | 설명 |
|---|---|
memory | 로컬 keypair 서명 (기본값) |
vault | HashiCorp Vault Transit 엔진 |
aws_kms | Ed25519를 사용한 AWS KMS |
gcp_kms | Ed25519를 사용한 GCP Cloud KMS |
privy | Privy 임베디드 지갑 |
turnkey | Turnkey API |
fireblocks | Fireblocks MPC |
cdp | Coinbase Developer Platform |
crossmint | Crossmint 관리형 지갑 |
dfns | Dfns 지갑 인프라 |
openfort | Openfort 임베디드 지갑 |
para | Para MPC 지갑 |
utila | Utila MPC 수탁 |
fordefi | Fordefi MPC 커스터디 |
ledger | USB-HID를 통한 Ledger 하드웨어 지갑 (Rust 전용, 선택 사항, all에 포함되지 않음) |
all | ledger를 제외한 모든 백엔드 활성화 |
sdk-v2 | Solana SDK 2.x (기본값) |
sdk-v3 | Solana SDK 3.x |
sdk-v4 | Solana SDK 4.x (v1 트랜잭션에 필요) |
unsafe-debug | 프로바이더의 전체 오류 응답을 로그에 기록하므로 프로덕션에서는 절대 활성화하지 마세요 |
sdk-v2, sdk-v3, sdk-v4 중 정확히 하나만 활성화해야 합니다.
기본 사용법
모든 서명자는 기본 SolanaSigner 트레이트(pubkey, sign_message, is_available)를 구현합니다. 또한 각 백엔드는 다음 기능 트레이트 중 정확히 하나를 구현합니다.
TransactionSigner: 전달받은 트랜잭션을 그대로 서명합니다.ModifyingSigner: 프로바이더가 트랜잭션을 수정한 후 서명합니다.SendingSigner: 프로바이더가 서명하고 브로드캐스트합니다.
use solana_keychain::{SignerError, SolanaSigner, TransactionSigner};use solana_sdk::transaction::VersionedTransaction;async fn sign_transaction(signer: &impl TransactionSigner,tx: &mut VersionedTransaction,) -> Result<(), SignerError> {// Get the signer's public keylet pubkey = signer.pubkey();println!("Signing with: {}", pubkey);// Check availability (useful for remote signers)if !signer.is_available().await {return Err(SignerError::NotAvailable("Signer offline".into()));}// Sign the transaction: returns Complete or Partiallet result = signer.sign_transaction(tx).await?;let (base64_tx, signature) = result.into_signed_transaction();println!("Signature: {}", signature);Ok(())}
.into()로 레거시 Transaction을 변환하세요. 레거시 및 v0 트랜잭션은 모든 SDK 기능에서 작동하며, v1 트랜잭션에는 sdk-v4가 필요합니다.
서명자 기능
| 백엔드 | 기능 트레이트 | sign_message |
|---|---|---|
| Crossmint | SendingSigner | 지원 안 됨 |
| Fordefi 네이티브 자동 | SendingSigner | 예 |
| Fordefi 네이티브 수동 | ModifyingSigner | 예 |
| Utila | TransactionSigner | 지원 안 됨 |
| CDP | TransactionSigner | UTF-8 페이로드만 가능 |
| Ledger | TransactionSigner | 예, 오프체인 엔벨로프를 통해 지원 |
| 그 외 모든 백엔드 (Fordefi 블랙박스 포함) | TransactionSigner | 예 |
Signer::from_* 생성자는 init()을 자동으로 실행합니다. Privy, Fireblocks, Dfns, Crossmint, Para, Openfort 또는 Utila 서명자 타입을 직접 생성하는 경우에는 사용하기 전에 init().await를 호출하세요.
백엔드 구성
Memory Signer
로컬 keypair를 사용한 개발 및 테스트용:
use solana_keychain::Signer;// From base58 private keylet signer = Signer::from_memory("base58_private_key")?;// From JSON keypair file pathlet signer = Signer::from_memory_file("/path/to/keypair.json")?;// From byte array stringlet signer = Signer::from_memory("[1,2,3,...]")?;
HashiCorp Vault
Transit 엔진을 사용하는 자체 호스팅 HSM용:
use solana_keychain::Signer;let signer = Signer::from_vault("https://vault.example.com:8200".to_string(), // Vault address"hvs.xxxxx".to_string(), // Vault token"my-solana-key".to_string(), // Key name in Transit"base58_public_key".to_string(), // Expected public keyNone, // Default HTTP timeouts)?;
AWS KMS
AWS KMS Ed25519 키를 사용한 클라우드 네이티브 서명용:
use solana_keychain::Signer;let signer = Signer::from_aws_kms("alias/my-solana-key".to_string(), // KMS key ID or alias"base58_public_key".to_string(), // Expected public keySome("us-east-1".to_string()), // Region (optional)).await?;
Privy
임베디드 지갑 서명용:
use solana_keychain::Signer;let signer = Signer::from_privy("app_id".to_string(), // Privy app ID"app_secret".to_string(), // Privy app secret"wallet_id".to_string(), // Wallet IDNone, // Default HTTP timeouts).await?;
Turnkey
비수탁형 서명용:
use solana_keychain::Signer;let signer = Signer::from_turnkey("api_public_key".to_string(), // Turnkey API public key"api_private_key".to_string(), // Turnkey API private key"org_id".to_string(), // Organization ID"private_key_id".to_string(), // Private key ID in Turnkey"base58_public_key".to_string(),// Expected public keyNone, // Default HTTP timeouts)?;
Fireblocks
기관용 MPC 서명:
use solana_keychain::{Signer, FireblocksSignerConfig};let config = FireblocksSignerConfig {api_key: "api_key".to_string(),private_key_pem: "-----BEGIN RSA PRIVATE KEY-----\n...".to_string(),vault_account_id: "0".to_string(),asset_id: Some("SOL".to_string()), // None defaults to "SOL"; "SOL_TEST" for devnetapi_base_url: None,poll_interval_ms: None,max_poll_attempts: None,use_program_call: None, // Some(true) signs via PROGRAM_CALL (sign-only, legacy/v0, hot wallet)http_client_config: None,};let signer = Signer::from_fireblocks(config).await?;
GCP KMS
GCP Cloud KMS Ed25519 키를 사용한 클라우드 네이티브 서명:
use solana_keychain::Signer;let signer = Signer::from_gcp_kms("projects/my-project/locations/us-east1/keyRings/my-ring/cryptoKeys/my-key/cryptoKeyVersions/1".to_string(),"base58_public_key".to_string(),).await?;
CDP (Coinbase Developer Platform)
Coinbase 관리형 지갑 서명:
use solana_keychain::{Signer, CdpSignerConfig, CDP_NETWORK_MAINNET};let signer = Signer::from_cdp(CdpSignerConfig {api_key_id: "api_key_id".to_string(), // CDP API key IDapi_key_secret: "api_key_secret".to_string(), // CDP API private key (base64 Ed25519)wallet_secret: "wallet_secret".to_string(), // CDP wallet secret (base64 PKCS#8 DER)address: "base58_address".to_string(), // Solana account addressnetwork: Some(CDP_NETWORK_MAINNET.to_string()), // Required to sign transactions with address lookup tablesapi_base_url: None,http_client_config: None, // Default HTTP timeouts})?;
CDP의 sign_message는 UTF-8 페이로드만 허용합니다.
Crossmint
Crossmint는 전송 전용입니다. API가 승인된 모든 트랜잭션을 서버 측에서 실행하므로 서명 전용 경로가 없으며 sign_message는 실패합니다. sign_and_send_transaction을 호출하세요. 이 함수는 트랜잭션을 그대로 두고, 실제로 반영된 트랜잭션의 서명을 반환합니다. Crossmint가 가스비를 후원하는 경우 해당 트랜잭션은 사용자의 트랜잭션과 다를 수 있습니다.
use solana_keychain::{Signer, CrossmintSignerConfig};let config = CrossmintSignerConfig {api_key: "api_key".to_string(),wallet_locator: "wallet_locator".to_string(),signer_secret: None,signer: None,api_base_url: None,poll_interval_ms: None,max_poll_attempts: None,};let signer = Signer::from_crossmint(config).await?;let signature = signer.as_sending_signer().expect("Crossmint is a sending signer").sign_and_send_transaction(&tx).await?;
Dfns
Dfns 지갑 인프라 서명:
use solana_keychain::{Signer, DfnsSignerConfig};let config = DfnsSignerConfig {auth_token: "auth_token".to_string(),cred_id: "cred_id".to_string(),private_key_pem: "-----BEGIN EC PRIVATE KEY-----\n...".to_string(),wallet_id: "wallet_id".to_string(),api_base_url: None,http_client_config: None,};let signer = Signer::from_dfns(config).await?;
Para
Para MPC 지갑 서명:
use solana_keychain::Signer;let signer = Signer::from_para("api_key".to_string(), // Para API key"wallet_id".to_string(), // Para wallet IDNone, // Default API base URL).await?;
Openfort
Openfort 백엔드 지갑 서명용:
use solana_keychain::Signer;let signer = Signer::from_openfort("sk_live_...".to_string(), // Openfort secret key"acc_...".to_string(), // Account ID"wallet_secret".to_string(), // Wallet secret (base64 PKCS#8 DER or PEM)None, // Default HTTP timeouts).await?;
Utila
Utila MPC 커스터디 서명용입니다. sign_message는 지원되지 않으며, 서명된 트랜잭션은 직접 브로드캐스트해야 합니다.
use solana_keychain::{Signer, UtilaSignerConfig};let signer = Signer::from_utila(UtilaSignerConfig {service_account_email: "sa@example.com".to_string(),service_account_private_key_pem: "-----BEGIN PRIVATE KEY-----\n...".to_string(),vault_id: "vault_id".to_string(),wallet_id: "wallet_id".to_string(),network: "networks/solana-devnet".to_string(),api_base_url: None,poll_interval_ms: None,max_poll_attempts: None,designated_signers: None,http_client_config: None,}).await?;
Fordefi
Fordefi MPC 커스터디 서명용입니다. API 요청은 ECDSA P-256 요청 서명으로 인증됩니다. private_key_pem 또는 request_signer(Arc<dyn FordefiRequestSigner>, 해당 키를 KMS/HSM에 보관할 때 사용) 중 정확히 하나만 제공하세요.
Fordefi는 세 가지 서명자 타입으로 제공되며, 생성 시점에 chain과 push_mode에 따라 결정됩니다.
chain | push_mode | 타입 | 기능 | 동작 |
|---|---|---|---|---|
None | any | FordefiBlackBoxSigner | TransactionSigner | 원시 바이트에 서명하며, 브로드캐스트는 직접 수행 |
Some(..) | None 또는 Auto | FordefiNativeAutoSigner | SendingSigner | Fordefi가 블록해시와 수수료를 수정할 수 있으며, 서명 후 브로드캐스트 |
Some(..) | Manual | FordefiNativeManualSigner | ModifyingSigner | Fordefi가 수정하고 서명하며, 반환된 트랜잭션은 직접 브로드캐스트 |
네이티브 수동 모드는 사용자의 트랜잭션을 Fordefi가 서명한 트랜잭션으로 대체합니다. 직접 만든 트랜잭션이 아니라 반드시 이 트랜잭션을 이어서 사용하세요. 다른 필수 서명자보다 먼저 실행해야 합니다.
use solana_keychain::{Signer, FordefiSignerConfig, FordefiPushMode, SolanaChainUniqueId, FordefiSolanaFee,FordefiPriorityLevel,};let config = FordefiSignerConfig {access_token: "api_token".to_string(),vault_id: "vault_uuid".to_string(),private_key_pem: Some(std::fs::read_to_string("./secret/private.pem")?),request_signer: None, // Or Some(Arc::new(your_kms_signer))public_key: "base58_public_key".to_string(), // Solana vault addresschain: Some(SolanaChainUniqueId::SolanaDevnet), // None for black box modefee: Some(FordefiSolanaFee::Priority {priority_level: FordefiPriorityLevel::Medium,}),push_mode: Some(FordefiPushMode::Auto), // Manual: sign without broadcastingapi_base_url: None, // Default: https://api.fordefi.compoll_interval_ms: None, // Default: 2000max_poll_attempts: None, // Default: 50http_client_config: None, // Default HTTP timeouts};let signer = Signer::from_fordefi(config).await?;
Ledger
USB-HID를 통한 하드웨어 지갑 서명용입니다(Rust 전용). ledger 기능, Solana 앱이 실행 중인 잠금 해제된 기기가 필요하며, Linux에서는 Ledger의 udev 규칙도 필요합니다. Ledger는 현재 감사 대상에 포함되지 않습니다. 감사 현황을 참고하세요.
use solana_keychain::{LedgerConfig, Signer};let signer = Signer::from_ledger(LedgerConfig {confirm_pubkey_on_device: true,..LedgerConfig::default()}).await?;
sign_message는 원시 바이트가 아니라 페이로드를 감싼 오프체인 엔벨로프에 서명합니다. 검증하려면 solana_keychain::ledger::ledger_offchain_envelope로 다시 생성하세요. 무인 환경에서 사용할 때는 auto_open_app: false로 설정하세요. 기기에서 프롬프트를 거부하면 SignerError::UserRejected가 반환됩니다.
통합 Signer Enum
런타임에 백엔드를 선택하려면 Signer enum을 사용하세요:
use solana_keychain::{Signer, SolanaSigner, SignerError};async fn get_signer(backend: &str) -> Result<Signer, SignerError> {match backend {"memory" => Signer::from_memory("base58_key"),"vault" => Signer::from_vault(/* ... */),"kms" => Signer::from_aws_kms(/* ... */).await,_ => Err(SignerError::ConfigError("Unknown backend".into())),}}// Use it genericallylet signer = get_signer("kms").await?;let pubkey = signer.pubkey();
Signer는 SolanaSigner만 구현합니다. 기능은 as_transaction_signer(), as_modifying_signer() 또는 as_sending_signer()를 통해 사용하며, 백엔드가 해당 형태를 지원하지 않으면 None을 반환합니다.
if let Some(transaction_signer) = signer.as_transaction_signer() {let result = transaction_signer.sign_transaction(&mut tx).await?;}
또는 sign_and_send가 백엔드에 따라 자동으로 처리하게 할 수도 있습니다. 이 크레이트에는 RPC 클라이언트가 없으므로 브로드캐스트 로직은 직접 제공해야 하며, 전송형 백엔드에서는 클로저가 호출되지 않습니다.
let signature = signer.sign_and_send(&mut tx, |encoded| async move { broadcast(encoded).await }).await?;
오류 처리
SignerError::BroadcastUnconfirmed { provider_tx_id, idempotency_key, transaction_signature, .. }(Crossmint, Fordefi 네이티브 자동 또는 실패한 sign_and_send 클로저에서 발생)는 트랜잭션이 이미 반영되었을 수 있음을 의미합니다. 무작정 재시도하지 마세요. 먼저 provider_tx_id 또는 transaction_signature를 대조해 확인하거나, 바이트가 완전히 동일한 트랜잭션만 다시 전송하세요. SignerError에 대한 exhaustive match에는 이 분기가 필요합니다. 각 서명 방식이 보장하는 내용은 보안 모델을 참고하세요.
커스텀 Signer 추가
추가 키 관리 서비스를 통합하려면 Signer 추가 가이드를 참조하세요.
Is this page helpful?