# Recover keys and sign with rkit-cli

This procedure walks you through recovering a blockchain key and producing a raw signature from your independently held vault backup, using the `rkit-cli` tool. For the concepts, scope, and preparation behind it, read the [Self-custody exit overview](/products/custody/operations-and-maintenance/backup-and-restore/self-custody-exit-overview) first.

This page is for whoever runs the recovery. It assumes comfort with a command line and, for hardware-backed providers, basic PKCS#11 and HSM concepts.

HSM-backed and software-wrapped vaults only
This procedure doesn't apply to MPC vaults. For MPC key management, see [MPC backups and exit strategy](/products/custody/deployment/integrate-kms/mpc/backups).

## Prerequisites

Version requirement
This procedure requires Ripple Custody version 1.29 or later (released December 2025). The self-service HSM backup API in Step 1 didn't exist before that release. On an earlier version, this procedure doesn't apply yet: contact Ripple Support or your Customer Partner Engineer (CPE) to arrange a backup.

Also confirm that:

- You run the backup create and acknowledge operations from the root domain.
- You have the `rkit-cli` package that matches your HSM provider (see Step 3), ideally already installed from before the disaster.
- For hardware HSM providers, you have network connectivity from where you run `rkit-cli` to your HSM cluster.


## Step 1: Obtain your wrapped seed

The Ripple Custody vault-backup lifecycle produces a signed trusted-entity envelope that contains your vault's wrapped seed material. The lifecycle has five stages.

### 1. Create the backup request

Submit a `v0_CreateBackup` intent for the vault you need to recover, with backup type `VaultHsm`, from the root domain. Wait until Ripple Custody accepts and executes the intent.

### 2. Wait for processing to complete

Poll the backup's status with the Get Backup information or List Backups API operations until processing is complete.

### 3. Fetch the trusted-entity envelope

Call Get Backup trusted entity details to retrieve the envelope. It contains four fields: `encryptedSeed` (the wrapped seed material itself), `hash`, `signingKey`, and `signature`.

One-time reveal
You can call Get Backup trusted entity details successfully only before you acknowledge the backup (stage 5 below). Once you acknowledge a backup, the server permanently denies access to this envelope. There's no way to fetch it again. Save the full response securely and durably (see [Step 6](#step-6-clean-up-securely)) before you acknowledge.

### 4. Verify the envelope's signature

Before you trust anything in the envelope, verify its signature against Ripple's notary public key. Don't derive keys from an envelope whose signature you haven't verified.

### 5. Acknowledge the backup

Submit a `v0_AcknowledgeBackup` intent to confirm that you have taken custody of the backup.

Acknowledging is irreversible
Acknowledge only after you have durably and securely saved the envelope from stage 3. Acknowledging is the action that permanently closes off future access to it through the API.

To script this lifecycle instead of driving it by hand, use the `ripple-custody-client-lib-python` SDK. Its `vaults.backups.create()`, `.wait_completed()`, `.get_trusted_entity()`, `.verify_trusted_entity()`, and `.acknowledge()` methods implement these five stages directly.

## Step 2: Determine the derivation path for your account

`rkit-cli` derives and signs at a specific derivation path that you provide. It has no knowledge of which Ripple Custody account that path belongs to. There are two ways to find the right path for the account you're recovering.

### If the Custody API is reachable

Read the account's derivation path directly from the API. Call `GET /domains/{domainId}/accounts/{accountId}`. The path is at `data.providerDetails.keys[*].derivationPath`, with one entry per key ID (for example, `SECP256K1_CUSTODY_1`).

### If the Custody application is unavailable (the disaster case)

You can reconstruct any account's derivation path from the Ripple Custody numbering scheme, without any API access:

- Ripple Custody numbers every domain and account sequentially in creation order, starting at 1. Domain numbering is a single flat, global sequence. The UI lets you organize domains hierarchically (a domain can contain subdomains), but that nesting is organizational only and doesn't appear in the derivation path. Ripple Custody uses a subdomain's own index directly at the top level. For example, subdomain 4 uses `m/4'/...`, never `m/<parent>'/4'/...`. A domain has no derivation path of its own; only its accounts do.
- Given domain index `d` and account index `a`, an account can have up to four keys: `SECP256K1_CUSTODY_1` at `m/d'/a'` (Bitcoin, Ethereum, Tron, XRPL); `ED25519_CUSTODY_1` at `m/d'/a'` (most Ed25519 chains); `ED25519_CUSTODY_2` at `m/d'/a'/0'` (Cardano spending); and `ED25519_STAKING_1` at `m/d'/a'/1'` (Cardano staking).
- UTXO-based chains (for example, Bitcoin) append `/change/address_index` to the account path. `change` is `0` for a deposit (external) address or `1` for a change (internal) address. `address_index` increments each time Ripple Custody generates a new address.


| Domain | Account | Derivation path |
|  --- | --- | --- |
| Domain 1 | Account 1 | `m/1'/1'` |
| Domain 1 | Account 2 | `m/1'/2'` |
| Domain 2 | Account 1 | `m/2'/1'` |


## Step 3: Obtain rkit-cli

Ripple shares the `rkit-cli` package for your HSM provider with you through SendSafely, typically as part of onboarding, well ahead of any disaster. Store this package on secure, durable storage alongside your other recovery materials.

Install the package for your platform:

```bash
# Debian/Ubuntu
sudo dpkg -i rkit-cli-<provider>_<version>_<arch>.deb

# RHEL/CentOS/Fedora
sudo rpm -i rkit-cli-<provider>-<version>.<arch>.rpm
```

## Step 4: Derive the account's key and verify it

```bash
rkit-cli derive --provider <provider> --pin-file <pin-file> --partition <partition> \
  --seed <seed-file> --path "<derivation-path>" --blockchain <chain>
```

This prints the derivation path (echoed back), the account's public key (in several encodings), its chain code, and its blockchain address.

Trust, but verify
Before you use this key to sign anything, compare the printed public key and address against the value Ripple Custody shows for this account (from the application, or from the API response in Step 2). An exact match confirms that you have the right seed file, the right derivation path, and the right provider configuration before you commit to signing.

## Step 5: Sign your payload

```bash
rkit-cli sign --provider <provider> --pin-file <pin-file> --partition <partition> \
  --seed <seed-file> --path "<derivation-path>" --blockchain <chain> \
  --hex <payload-hex>
```

The `--blockchain` flag automatically selects the correct curve, pre-hash, and address format for that chain. For the full table, see the [chain signature reference](#chain-signature-reference). You can also supply the payload from a file with `--file` instead of `--hex`.

The output includes the raw signature, low-S normalized where the chain requires it, and a DER-encoded form where applicable.

## Step 6: Clean up securely

- Prefer `--pin-file` over `--pin`. The PIN then never appears in `ps` output or your shell history.
- Treat the seed envelope and any PIN file as sensitive material for as long as they exist on disk. Use access-controlled, ideally encrypted, storage only, never an unencrypted shared drive.
- After you confirm that the recovery succeeded, securely delete (shred, don't just remove) the seed envelope file and any PIN file.


## Connect to your HSM provider

Steps 1, 2, 4, 5, and 6 are identical regardless of provider. The following sections cover only what differs: the authentication flags, how `rkit-cli` reaches your HSM, and a worked example. Use the section that matches the HSM provider your organization deployed. For the complete flag list, see the [rkit-cli quick reference](#rkit-cli-quick-reference).

### Thales Luna

- `-p`/`--pin` or `--pin-file`, and `-n`/`--partition <serial-or-label>` (required).
- `--lib-path` overrides the dlopen path (it defaults to the vendor's documented install location).
- `--debug-pkcs11` loads the vendor's logging library variant, which is useful when you diagnose a connection problem with Thales support.


Luna requires a working NTLS connection to your Luna HSM or HA group: a `Chrystoki.conf` (client certificate and key, CA certificate, and HSM server host and port) and the Luna Minimal Client PKCS#11 library installed. `rkit-cli` only consumes an already-configured library. To set these up, see Thales's own client installation and network documentation.

```bash
rkit-cli derive --provider luna --pin-file luna.pin --partition partition-2 \
  --seed blobfile.sim --path "m/2'/1'/0/0" --blockchain bitcoin

rkit-cli sign --provider luna --pin-file luna.pin --partition partition-2 \
  --seed blobfile.sim --path "m/2'/1'/0/0" --blockchain bitcoin \
  --hex <sighash-hex>
```

### Securosys Primus

- `-p`/`--pin` or `--pin-file`, and `-n`/`--partition`.
- `--lib-path` defaults to `/usr/local/primus/lib/libprimusP11.so`, or set the `PKCS11_LIBRARY_PATH` environment variable.
- The seed file (`--seed`) is the `encryptedSeed` envelope from Step 1, passed as-is. It's a per-curve wrapped master key.


Primus requires the Securosys Primus PKCS#11 client library installed and network connectivity to your Primus HSM cluster. See Securosys's own client installation documentation.

```bash
rkit-cli derive --provider primus --pin-file primus.pin --partition primus-1 \
  --seed primus_seed.bin --path "m/1'/1'" --blockchain ethereum \
  --lib-path /opt/primus/lib/libprimusP11.so
```

### Utimaco CryptoServer

- `-p`/`--pin` or `--pin-file`.
- `-n`/`--partition` is a numeric slot ID here, not a label.
- `--lib-path` carries the CXI device address (for example, `3001@my.hsm.host` or `/dev/cs2`), not a filesystem path, despite the flag name.


Utimaco requires the Utimaco CryptoServer PKCS#11 (`libcs_pkcs11_R2.so`) and CXI (`libcxi.so`) client libraries installed and reachable. See Utimaco's own client installation and network documentation.

```bash
rkit-cli derive --provider utimaco --pin-file utimaco.pin --partition 1 \
  --seed utimaco_seed.bin --path "m/2'/1'/0/0" --blockchain bitcoin \
  --lib-path 3001@my.hsm.host
```

### IBM Cloud HPCS EP11

- There's no local PKCS#11 library. `rkit-cli` connects to a remote gRPC endpoint instead.
- `--endpoint <host[:port]>` (default port 443).
- `--token`/`--token-file` (a bearer token) or `--api-key`/`--api-key-file` (an IAM API key, exchanged for a bearer token). Pass exactly one of the two.
- `--iam-endpoint`, `--instance`, and `--ca-file` are optional, for non-default IAM endpoints, Bluemix instance metadata, or a custom CA bundle.


IBM Cloud HPCS EP11 requires outbound network access from wherever you run `rkit-cli` to your IBM Cloud HPCS EP11 endpoint, and to IBM's IAM token endpoint if you use an API key. There's no local vendor library to install.

```bash
rkit-cli derive --provider ibm --endpoint hpcs.example.ibm.com:443 \
  --token-file ibm.token --seed ibm_seed.bin \
  --path "m/2'/1'/0/0" --blockchain bitcoin
```

## Troubleshoot

`rkit-cli` reports a specific, plain-language reason for every failure it can anticipate. The following table lists the most common messages.

| Message or symptom | What it means | What to do |
|  --- | --- | --- |
| `MissingPin` | You supplied neither `--pin` nor `--pin-file`. | Supply one. Prefer `--pin-file`. |
| `MissingPartition` | You gave no `--partition` for a provider that requires one (always for Luna and Utimaco; optional for Primus). | Pass `-n`/`--partition` with your HSM partition's serial number, label, or (Utimaco) numeric slot ID. |
| `...failed. PKCS#11 error: CKR_PIN_INCORRECT` | The PIN doesn't match the PIN on the targeted HSM partition. | Double-check the PIN value or file, and confirm that you're targeting the correct partition. |
| `no slot matches --partition 'X' (searched N slot(s))` / `TokenNotPresent` / `NoMatchingSlot` | The partition name, label, or serial number you gave doesn't match any slot the HSM client can currently see. | Confirm the exact label or serial with your HSM administrator, and confirm network connectivity to the HSM. |
| `failed to open provider '...': FileNotFound` (while loading the library) | `rkit-cli` couldn't find or load the vendor PKCS#11 library. | Check `--lib-path`, and confirm that you installed the vendor's client software. |
| `symbol '...' not found in ... library` (`SymbolNotFound`) | `rkit-cli` loaded the wrong version of the vendor library, or an otherwise incompatible one. | Confirm that you have the vendor library version `rkit-cli` expects. |
| `failed to read file '...', err: FileNotFound` (seed file) | The path you gave to `--seed` doesn't exist. | Confirm the path to your saved seed envelope. |
| `failed to load seed: EmptyEnvelope / InvalidEnvelopeBase64 / InvalidEnvelopeJson / InvalidProtobuf / UnknownCurveInEnvelope / MissingSecretMaterial` | The seed file is empty, corrupted, truncated, or doesn't contain material for the curve you requested. | Re-fetch or re-copy the envelope, confirm that you saved the entire response from Get Backup trusted entity details exactly as returned, and confirm that `--curve`/`--blockchain` matches a curve present in the envelope. |
| `--pin and --pin-file are mutually exclusive; pass exactly one` | You passed both flags at once. | Pass only one. Prefer `--pin-file`. |
| `--path is required` | You omitted the `--path`/`-d` flag. | Supply `-d`/`--path` with the derivation path (see Step 2). |
| `failed to parse HD path '...', err: InvalidCharacter / Overflow` | The derivation path string is malformed. | Check the path format, for example `m/1'/1'` (see Step 2). |


## rkit-cli quick reference

| Flag | Purpose |
|  --- | --- |
| `--provider` | The HSM provider: `luna`, `primus`, `utimaco`, or `ibm`. |
| `-p`/`--pin` or `--pin-file` | The HSM PIN, inline or from a file. Prefer `--pin-file` so the PIN never appears in `ps` output or shell history. Pass exactly one. |
| `-n`/`--partition` | The HSM partition. A serial number or label for Luna and Primus; a numeric slot ID for Utimaco. Required for Luna and Utimaco; optional for Primus. |
| `--seed` | The path to your saved `encryptedSeed` envelope. |
| `-d`/`--path` | The derivation path, for example `m/1'/1'`. |
| `--blockchain` | The target chain. Selects the curve, pre-hash, and address format automatically (see the [chain signature reference](#chain-signature-reference)). |
| `--curve` | Expert mode: `secp256k1` or `ed25519`. Signs the same raw payload under every pre-hash `rkit-cli` knows for that curve, which is useful for confirming exactly which hash a given network expects. |
| `--hex` / `--file` | The payload to sign, inline as hex or from a file. |
| `--lib-path` | The vendor PKCS#11 library location. Its meaning varies by provider (see [Connect to your HSM provider](#connect-to-your-hsm-provider)). |


### Vendor configuration files

`rkit-cli` consumes each HSM vendor's own configuration files and client libraries. It doesn't define their format. For the exact file format and available settings, see the official vendor documentation.

| Provider | Configuration files | Contents |
|  --- | --- | --- |
| Thales Luna | `Chrystoki.conf` | Client-wide PKCS#11 configuration: NTLS server, HA group, and logging settings. |
| Securosys Primus | `primus.cfg` / `.secrets.cfg` | `primus.cfg` holds connectivity and partition settings; `.secrets.cfg` holds the ciphered permanent secret that authenticates to the HSM. |
| Utimaco CryptoServer | `cs_pkcs11_R2.cfg` | PKCS#11 R2 module configuration: device address, timeouts, and logging. |
| IBM Cloud HPCS EP11 | None | You configure the connection entirely through CLI flags (`--endpoint`, `--token`, `--api-key`). |


## Chain signature reference

`--blockchain <name>` automatically selects the signature scheme, elliptic curve, and pre-hash applied before signing for every chain `rkit-cli` supports.

| Chain | Scheme | Curve | Pre-hash before signing |
|  --- | --- | --- | --- |
| Bitcoin | ECDSA | secp256k1 | double-SHA-256 (sighash) |
| Ethereum | ECDSA | secp256k1 | Keccak-256 over typed-tx + RLP |
| Tron | ECDSA | secp256k1 | SHA-256 over protobuf `raw_data` |
| XRPL | ECDSA | secp256k1 | SHA-512Half (SHA-512 truncated to 256 bits) |
| Algorand | EdDSA | Ed25519 | Pure Ed25519 over `"TX" |
| Cardano | EdDSA | Ed25519 | Blake2b-256 over the CBOR tx body |
| Diem | EdDSA | Ed25519 | SHA3-256 domain-separator prefix |
| Hedera | EdDSA | Ed25519 | Pure Ed25519 over `TransactionBody` |
| Polkadot / Substrate | EdDSA | Ed25519 | Blake2b-256 when the SCALE payload is larger than 256 bytes |
| Solana | EdDSA | Ed25519 | Pure Ed25519 over the message bytes |
| Stellar | EdDSA | Ed25519 | SHA-256 over the signature payload |
| Tezos (tz1) | EdDSA | Ed25519 | Blake2b-256 over `0x03 |


Pure Ed25519 chains feed the serialized message into Ed25519 directly. Ed25519 runs SHA-512 internally as part of the signature, but there's no application-level pre-hash step beyond that.

## Next steps

- [Self-custody exit overview](/products/custody/operations-and-maintenance/backup-and-restore/self-custody-exit-overview): concepts, scope, and preparation.
- [Back up, restore, and recover](/products/custody/operations-and-maintenance/backup-and-restore): all Ripple Custody backup and recovery procedures.