Skip to content

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 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.

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) 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.
DomainAccountDerivation path
Domain 1Account 1m/1'/1'
Domain 1Account 2m/1'/2'
Domain 2Account 1m/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:

# 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

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

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. 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.

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.

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.

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.

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.

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 symptomWhat it meansWhat to do
MissingPinYou supplied neither --pin nor --pin-file.Supply one. Prefer --pin-file.
MissingPartitionYou 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_INCORRECTThe 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 / NoMatchingSlotThe 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 / MissingSecretMaterialThe 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 oneYou passed both flags at once.Pass only one. Prefer --pin-file.
--path is requiredYou omitted the --path/-d flag.Supply -d/--path with the derivation path (see Step 2).
failed to parse HD path '...', err: InvalidCharacter / OverflowThe derivation path string is malformed.Check the path format, for example m/1'/1' (see Step 2).

rkit-cli quick reference

FlagPurpose
--providerThe HSM provider: luna, primus, utimaco, or ibm.
-p/--pin or --pin-fileThe 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/--partitionThe 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.
--seedThe path to your saved encryptedSeed envelope.
-d/--pathThe derivation path, for example m/1'/1'.
--blockchainThe target chain. Selects the curve, pre-hash, and address format automatically (see the chain signature reference).
--curveExpert 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 / --fileThe payload to sign, inline as hex or from a file.
--lib-pathThe vendor PKCS#11 library location. Its meaning varies by provider (see 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.

ProviderConfiguration filesContents
Thales LunaChrystoki.confClient-wide PKCS#11 configuration: NTLS server, HA group, and logging settings.
Securosys Primusprimus.cfg / .secrets.cfgprimus.cfg holds connectivity and partition settings; .secrets.cfg holds the ciphered permanent secret that authenticates to the HSM.
Utimaco CryptoServercs_pkcs11_R2.cfgPKCS#11 R2 module configuration: device address, timeouts, and logging.
IBM Cloud HPCS EP11NoneYou 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.

ChainSchemeCurvePre-hash before signing
BitcoinECDSAsecp256k1double-SHA-256 (sighash)
EthereumECDSAsecp256k1Keccak-256 over typed-tx + RLP
TronECDSAsecp256k1SHA-256 over protobuf raw_data
XRPLECDSAsecp256k1SHA-512Half (SHA-512 truncated to 256 bits)
AlgorandEdDSAEd25519Pure Ed25519 over `"TX"
CardanoEdDSAEd25519Blake2b-256 over the CBOR tx body
DiemEdDSAEd25519SHA3-256 domain-separator prefix
HederaEdDSAEd25519Pure Ed25519 over TransactionBody
Polkadot / SubstrateEdDSAEd25519Blake2b-256 when the SCALE payload is larger than 256 bytes
SolanaEdDSAEd25519Pure Ed25519 over the message bytes
StellarEdDSAEd25519SHA-256 over the signature payload
Tezos (tz1)EdDSAEd25519Blake2b-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