> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genlabs.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign with an external KMS

> Use the Grid Rust SDK with an Ed25519 key held in AWS CloudHSM, GCP KMS, Azure Key Vault, HashiCorp Vault, or any external signing oracle; without ever loading the private key into your process.

The Grid Rust SDK separates *building* an activation from *signing* it. Anything that can produce a 64-byte Ed25519 signature over a byte string is a valid signer. That lets you keep private keys in an HSM or KMS and have the SDK call out to it for the one operation that needs the key.

This guide shows how to plug an external signing oracle into [`GenClient`](https://docs.rs/client-sdk) by implementing the `EddsaSigner` trait. The worked example uses placeholders for the KMS call so you can drop in your provider's SDK; the structure is the same for AWS CloudHSM, GCP KMS, Azure Key Vault Managed HSM, or HashiCorp Vault transit.

## Prerequisites

* Rust 1.75+ and a project that depends on the Grid `client-sdk` crate.
* An external key store that can sign **raw Ed25519 (RFC 8032)** without adding any extra hashing or domain separation.
* The 32-byte Ed25519 public key for that KMS-resident key, fetched once at boot.

> **AWS KMS specifically does not support Ed25519** as of this writing. Its asymmetric key specs are RSA, NIST P-curves, secp256k1, and SM2. If you need Ed25519 on AWS, use **AWS CloudHSM** (which exposes Ed25519 via PKCS#11) or AWS KMS [External Key Store (XKS)](https://docs.aws.amazon.com/kms/latest/developerguide/keystore-external.html) backed by an HSM that does. Providers with native Ed25519 signing include **GCP KMS** (`EC_SIGN_ED25519`), **Azure Key Vault Managed HSM** (`EdDSA`), and **HashiCorp Vault** transit (`ed25519`).

## How the SDK splits the work

`EddsaSigner` is the SDK's signing-oracle interface. The trait has two required methods:

```rust theme={null}
#[async_trait(?Send)]
pub trait EddsaSigner {
    type Error: std::error::Error + Send + Sync + 'static;

    fn public_key(&self) -> &GenPublicKey;
    async fn sign_bytes(&self, message: &[u8]) -> Result<EddsaSignature, Self::Error>;
}
```

The contract:

* `public_key()` returns a cached `GenPublicKey`. **It must not perform I/O**; fetch the key once at construction and store it.
* `sign_bytes()` signs `message` with raw Ed25519. The SDK supplies a canonical 82-byte activation pre-image; do not hash it again or add domain separation.
* The default `sign_activation()` extracts that pre-image and forwards to `sign_bytes()`; you almost never override it.

That's the whole adapter surface. Everything else (building the canonical activation, attaching the signature, submitting to RPC, polling for finality) stays in the SDK.

## Step 1: Construct the public key handle

Fetch the public key from your KMS once at boot, then wrap it in a `GenPublicKey`. The wire format is the raw 32-byte Ed25519 public key.

```rust theme={null}
use client_sdk::GenPublicKey;

let public_key_bytes: [u8; 32] = kms_get_public_key(&kms, &key_id).await?;
let public_key = GenPublicKey::from_bytes(&public_key_bytes)?;

// The canonical Grid address derived from this public key:
println!("address: {}", public_key.entity_id());
```

`GenPublicKey::from_bytes` does the curve check; if the KMS returned a key that isn't a valid Ed25519 point, you find out here rather than at signing time.

## Step 2: Implement `EddsaSigner`

A KMS adapter is a struct that holds your KMS client handle, the key identifier, and the cached public key. The mock signer in [client-sdk/src/test\_utils/mock\_eddsa\_signer.rs](https://github.com/gen-bc/gen/blob/main/metacomputer/gen-framework/client-and-test-utils/client-sdk/client-sdk/src/test_utils/mock_eddsa_signer.rs) shows the same shape backed by `ed25519-dalek`.

```rust theme={null}
use async_trait::async_trait;
use client_sdk::{EddsaSigner, EddsaSignature, GenPublicKey};
use thiserror::Error;

pub struct KmsSigner {
    kms: KmsClient,           // your provider's SDK client
    key_id: String,           // KMS key identifier
    public_key: GenPublicKey, // fetched once at construction
}

#[derive(Debug, Error)]
pub enum KmsSignerError {
    #[error("KMS sign failed: {0}")]
    Sign(#[source] kms_sdk::Error),
    #[error("KMS returned a {0}-byte signature; Ed25519 is 64")]
    InvalidLength(usize),
}

impl KmsSigner {
    pub async fn new(kms: KmsClient, key_id: String) -> Result<Self, KmsSignerError> {
        let pk_bytes = kms_get_public_key(&kms, &key_id).await
            .map_err(KmsSignerError::Sign)?;
        let public_key = GenPublicKey::from_bytes(&pk_bytes)
            .map_err(|e| KmsSignerError::Sign(e.into()))?;
        Ok(Self { kms, key_id, public_key })
    }
}

#[async_trait(?Send)]
impl EddsaSigner for KmsSigner {
    type Error = KmsSignerError;

    fn public_key(&self) -> &GenPublicKey {
        &self.public_key
    }

    async fn sign_bytes(&self, message: &[u8]) -> Result<EddsaSignature, Self::Error> {
        // Replace this call with your KMS's "sign raw Ed25519" API.
        // GCP KMS:    AsymmetricSign with digest=None and message=raw_bytes
        // Azure MHSM: SignRawEd25519
        // Vault:      transit/sign/<key>/ed25519 with hash_algorithm=none
        // CloudHSM:   PKCS#11 C_SignInit + C_Sign with CKM_EDDSA
        let sig_bytes: Vec<u8> = self.kms.sign_ed25519(&self.key_id, message).await
            .map_err(KmsSignerError::Sign)?;

        let sig: [u8; 64] = sig_bytes.as_slice().try_into()
            .map_err(|_| KmsSignerError::InvalidLength(sig_bytes.len()))?;
        Ok(EddsaSignature::new(sig))
    }
}
```

The trait is `?Send` to keep the SDK WASM-compatible. If your KMS client is `Send + Sync` (it usually will be in a server), you can add those bounds at your own use sites without touching the trait.

## Step 3: Submit a transfer

`GenClient::sign_and_submit_activation` is generic over `EddsaSigner`; pass your KMS adapter and the SDK does the rest:

```rust theme={null}
use client_sdk::{ActivationPayload, GenClient, GenClientConfig};

let client = GenClient::connect(GenClientConfig::devnet()).await?;
let signer = KmsSigner::new(kms_client, "alias/gen-treasury".into()).await?;

let payload: ActivationPayload = build_transfer_payload(/* to, amount */);

let activation_id = client
    .sign_and_submit_activation(&signer, &payload)
    .await?;

let outcome = client.wait(activation_id).await?;
```

Internally the call:

1. Builds the canonical unsigned activation against `signer.public_key()`.
2. Hands the 82-byte pre-image to `signer.sign_bytes()`.
3. Attaches the returned signature and submits to the RPC endpoint.

If your KMS adapter returns `KmsSignerError::Sign(...)`, it surfaces as `SdkError::EddsaSigner(_)` with the original error preserved as the `source`.

## Operational notes

* **Latency:** A KMS round-trip per transaction is the dominant cost. Budget for it in any timeout you set (`SubmitActivationRequest`, `wait` strategy). For high-throughput agents, prefer regional endpoints and warm connections.
* **Public-key caching:** Fetch the public key once at boot. The trait contract forbids I/O in `public_key()`, and re-fetching on every call would double KMS load.
* **Retries:** `sign_bytes` runs after the activation pre-image is committed. If the KMS call fails transiently, it is safe to retry; the pre-image is byte-identical. Retries beyond the activation's `valid_until_block` will be rejected by the mempool, so retry quickly or rebuild.
* **Authorization:** Lock the KMS key policy down to the IAM role / service identity that runs your Grid client. Anyone who can call `sign` on that key can move funds from the corresponding account.
* **Audit:** KMS providers log every `sign` call. Pair the KMS audit log with the resulting `ActivationId` (logged by the SDK) to get an end-to-end audit trail.

## Bring-your-own without the trait

If your signing oracle can't be wrapped in an `async fn` (offline air-gapped flow, hardware wallet with a confirm-on-device prompt, etc.): drive the steps manually:

1. `GenPublicKey::from_bytes`: wrap the 32-byte public key.
2. `GenClient::build_signable_activation_with_options`: produce the canonical unsigned activation.
3. `SignableGenExternalActivation::signable_buffer`: extract the 82-byte pre-image.
4. Sign those bytes externally with raw Ed25519 (RFC 8032).
5. `EddsaSignature::new(sig_bytes)`: wrap the 64-byte signature.
6. `SignableGenExternalActivation::into_signed`: attach the signature.
7. `GenClient::submit_activation`: submit.

This is the same flow the trait runs internally, exposed as primitives.

## See also

* [`gen wallet`](/reference/cli/wallet): the local wallet store the CLI uses; not relevant once you've moved keys to a KMS.
