Hashi Node Backups
Hashi automatically backs up the data a node needs to rejoin the committee after
data loss. Every epoch, Hashi writes the node config, referenced key files,
and database snapshot to a tar archive, then compresses and encrypts it as an
ASCII-armored PGP message. The backup files use the suffix .tar.asc.
The key files and database snapshot can contain the node's operator, TLS, and
MPC private keys (see Suspected node compromise),
so treat a decrypted backup as sensitive key material.
Encryption only needs the public PGP certificate, so the backup key does not
need to be present on the server during normal operation. Restore needs access
to the matching private key, either as a local PGP secret-key file or through
gpg-agent for YubiKey-backed keys. Because you can forward gpg-agent over
SSH, you can restore on a remote server using a YubiKey plugged into your laptop
instead of plugging the YubiKey into the server itself.
Prerequisites
The section that uses each item describes it in detail:
- For a YubiKey backup key: a new YubiKey 5 Series key with firmware 5.7 or newer, a USB-capable machine, oct, GnuPG, YubiKey Manager, jq, and a checkout of the Hashi repository.
- For a local backup key file: sequoia-sq.
- For remote backups: a private S3 or GCS bucket, write-only node credentials, and separate recovery credentials kept off-node. See Remote backups for provider-specific authentication and safeguards.
Create a backup key
Option 1: Create a private key on a YubiKey (recommended)
We recommend generating a PGP private key entirely on one YubiKey and using its public key to encrypt the node backup. This makes it practically impossible for anyone to decrypt the backup without physical access to the YubiKey.
Do not lose the YubiKey. Without it, you cannot decrypt backups encrypted to its public key.
Use a USB-capable machine and a new YubiKey 5 Series with firmware 5.7 or newer. Connect only this YubiKey; disconnect other smartcards. Normal backups need only its public certificate, not a connected YubiKey.
- Install oct,
GnuPG (
gpgandgpgconf), YubiKey Manager (ykman), and jq. Ensure your system's smartcard service is available. - From a checkout of the Hashi repository, run the setup script in an interactive terminal:
bash scripts/node-operator/provision-yubikey-backup.sh --output-dir ./backup-key
- Follow the prompts for an optional User ID (Enter to omit), PIN changes, and physical touches. The script refuses cards that already contain keys, generates the private keys on-device, enables touch for decryption, exports public artifacts, and checks encryption and decryption. Keep the new User and Admin PINs in a secure password manager. Do not replace the factory attestation (ATT) key or certificate.
- Save these 4 public files.
<fingerprint>is the full encryption subkey fingerprint:yubikey-backup-<fingerprint>.ascyubikey-backup-<fingerprint>.attestation-device.pemyubikey-backup-<fingerprint>.attestation-dec.pemyubikey-backup-<fingerprint>.fingerprints.txt
Use the .asc file directly as backup-pgp-cert in your node configuration.
The script also creates YubiKey attestation evidence. If desired, share all 4
public files to assemble a multi-recipient backup certificate,
which can verify the attestations.
For YubiKey restores with --use-gpg-agent, import the public certificate and
link the card in your normal GnuPG home (the script's test keyring is temporary):
gpg --import "./backup-key/yubikey-backup-<fingerprint>.asc"
gpg --card-status
Replace <fingerprint> with the generated filename's fingerprint and keep the
YubiKey connected; if using a separate recovery GnuPG home, use it for both commands.
Preserve the filenames. Store the YubiKey securely; do not share it or the PINs. If setup fails, retain the public files and inspect the card before retrying. You can restart provisioning if no data depends on any newly generated keys. Otherwise, preserve the keys and follow the script's public-artifact recovery guidance: wiping or regenerating them would destroy access to existing backups.
Option 2: Create a local private key file
If you do not want to use a YubiKey, you can generate a PGP private key locally and encrypt with it. Because a normal file holds the private key instead of a YubiKey, there is a greater risk of unauthorized use of the private key. Store the file carefully.
-
Install tools:
- sequoia-sq, Sequoia PGP.
-
Generate a new key pair. Be sure to update the name and email to real values.
sq key generate \
--own-key \
--name "Your Name" \
--email "you@example.com" \
--expiration never \
--without-password \
--output private-key.asc \
--rev-cert revocation-cert.asc
- Export the public key to a file. Despite the name, this doesn't actually delete the private key, it just extracts the public key.
sq key delete \
--cert-file private-key.asc \
--output public-key.asc
- Test encrypting a file to the public key:
echo "test message" > test.txt
sq encrypt \
--for-file public-key.asc \
--without-signature \
--output test.txt.asc \
test.txt
- Test decrypting the file with the private key:
sq decrypt --recipient-file private-key.asc test.txt.asc
Confirm the output matches the original file contents, then delete the test files.
- Save the revocation, public key, and private key files in a secure location.
Assemble a multi-recipient backup certificate
You can combine multiple YubiKey recipients into one OpenPGP certificate. Any selected recipient can independently decrypt the backup; this is 1-of-n, not threshold recovery. Participants exchange only public certificates, so the YubiKeys never need to be in the same location. Hashi also supports software-key recipients.
This command requires the optional backup-assemble-cert feature. Build it with
cargo build -p hashi --release --features backup-assemble-cert; the binary is
target/release/hashi. Normal node builds omit this command and its GPG-agent dependencies.
The certifier's primary key must support certification and signing and be
available through gpg-agent. Pass its public certificate to --primary-cert:
hashi backup assemble-cert \
--primary-cert certifier-public.asc \
--recipient alice-public.asc \
--recipient bob-public.asc \
--output hashi-backup-cert.asc
Repeat --recipient for each public recipient certificate. If a certificate has
multiple usable encryption subkeys, select one with
--recipient "alice-public.asc=FULL_ENCRYPTION_SUBKEY_FINGERPRINT".
The certifier is not automatically a recipient; list it explicitly if needed.
Use --gpg-homedir PATH to select a different GnuPG home. The output must not
already exist; configure it as backup-pgp-cert below.
The command reports each key's expiry. A finite certifier expiry limits the whole certificate: replace it before then to avoid backup and node-startup failures. If the certifier advertises SEIPDv2, every recipient must support it; a v1-only certifier can include recipients that also support v2.
To verify a recipient's YubiKey provenance, add
--recipient-attestation SUBKEY_FINGERPRINT DEVICE_PEM DEC_PEM, supplying the
device attestation certificate and DEC statement. Repeat for each attested
recipient. Supplied evidence is always verified; add
--require-recipient-attestation to require it for every recipient.
For example, using the files exported during YubiKey setup (replace
FULL_ENCRYPTION_SUBKEY_FINGERPRINT with the [E] subkey fingerprint from the
recipient's .fingerprints.txt file, without spaces). Replace the recipient
paths with the matching files produced by the script:
hashi backup assemble-cert \
--primary-cert certifier-public.asc \
--recipient yubikey-backup-FULL_ENCRYPTION_SUBKEY_FINGERPRINT.asc \
--recipient-attestation FULL_ENCRYPTION_SUBKEY_FINGERPRINT \
yubikey-backup-FULL_ENCRYPTION_SUBKEY_FINGERPRINT.attestation-device.pem \
yubikey-backup-FULL_ENCRYPTION_SUBKEY_FINGERPRINT.attestation-dec.pem \
--require-recipient-attestation \
--output hashi-backup-cert.asc
For multiple YubiKeys, collect each recipient's public certificate, device PEM,
DEC PEM, and .fingerprints.txt file under distinct filenames. Repeat both
--recipient and --recipient-attestation for each recipient. With
--require-recipient-attestation, software-generated keys cannot be recipients.
Verification checks the device certificate against pinned Yubico issuers and checks that the DEC statement attests on-device generation of the selected key. It does not establish current possession of the YubiKey or enforce a touch policy, and it does not check X.509 expiry or revocation.
Before relying on the certificate, make a backup and confirm that each recipient can restore that same archive independently.
Run hashi backup test --backup-pgp-cert hashi-backup-cert.asc --use-gpg-agent
for a temporary-message round trip with a YubiKey or GnuPG key, or replace
--use-gpg-agent with --backup-pgp-secret-key private.asc for a local private key.
Test files are automatically cleaned up on success or error.
Configure Hashi backups
Every node config must provide backup-pgp-cert (a certificate file path or inline
armored text) and backup-dir:
backup-pgp-cert = "/path/to/hashi-backup-cert.asc"
backup-dir = "/var/lib/hashi/backups"
The backup-pgp-cert file is the public key you created in
Create a backup key: the yubikey-backup-<fingerprint>.asc
file from Option 1, public-key.asc from Option 2, or a certificate from
Assemble a multi-recipient backup certificate. It is the public half of the backup key pair.
Restore uses the matching private half, passed as --backup-pgp-secret-key
(for example, private-key.asc from Option 2) or reached through
--use-gpg-agent for YubiKey-backed keys.
Use a persistent, node-specific backup directory outside the database directory, with write access restricted to the node's user.
Automatic cleanup
On epoch housekeeping, before the save, Hashi applies these rules in backup-dir:
- Hashi deletes archives older than 14 days by filename timestamp, but keeps
the newest current-epoch MPC recovery archive until it saves a newer one or
that epoch ends. Ordinary backups cannot replace that recovery copy. Hashi
keeps the newest archive of any kind while the node is still attempting
saves, or up to 30 days otherwise. Archives that manual backups place in
backup-dirfollow the same policy. - Hashi removes abandoned
.hashi-backup-*files and.hashi-restore-*directories older than 14 days by modification time, without retaining the newest staging entry. - Hashi leaves recent staging entries, symlinks, and unrelated files alone.
- Entry errors do not stop the sweep, and a cleanup failure does not prevent the backup attempt.
Each sweep logs its epoch, directory, whether anyone requested a backup, and removed/failed
counts; expiring the newest archive also warns.
The node stages archives in backup-dir, then finalizes and syncs them to disk before
publication; it syncs the directory afterward on a best-effort basis. It logs a
directory sync failure at error level without failing the already-published backup. Failed saves
remove their temporary files and never overwrite existing archives.
What operators must clean up
Automatic cleanup does not cover the following; handle them yourself:
- A powered-off node sweeps nothing, so delete a decommissioned host's archives yourself.
- Manual backups do not trigger cleanup. Apply backup expiry to all copies except the monthly off-machine copies.
- Restore staging and completed restore output contain decrypted keys and
database data; use a private restore output directory. Automatic backup
cleanup does not sweep completed restores,
.hashi-db-restore-*staging directories beside the destination database, or restore output elsewhere. Remove these plaintext copies when you no longer need them.
Backup expiry
Use a 14-day retention target, measured from the archive's original creation, for every backup copy except the monthly off-machine copies: local archives (including the newest), other off-machine copies, and all versions or generations of remote uploads. Copying or re-uploading must not reset this target; Remote backups describes provider-specific versioning and deletion timing.
Active nodes check daily and refresh their current-epoch MPC recovery backup once it is 3 days old, even during stalled reconfiguration. This intentionally preserves recovery of the active shares; it does not extend retention for historical epochs. If refresh is failing, preserve the last usable recovery copy and follow the incident guidance below rather than deleting it to meet the target.
Old shares can still reconstruct the active MPC key after resharing. Keeping backups indefinitely gives an attacker more time to collect enough decryptable backups from the same epoch to recover that key. Expiry limits this opportunity while preserving a recent disaster-recovery window. You keep the monthly off-machine copies longer, for three months, so that you can recover the key even if an attacker wipes every node.
Operators must configure deletion in each storage location, leaving out only the monthly copies, which you destroy by hand under their own rule; Hashi's newest-backup exception alone does not enforce this policy. Account for retention locks and holds, and monitor deletion failures or delays. Remove temporary decrypted copies immediately after use.
During an incident, pause expiry for backups needed for recovery: disable the
applicable cloud lifecycle rules and other off-machine deletion jobs, and stop
destroying monthly copies. Hashi's local cleanup still runs, so preserve encrypted
copies outside backup-dir in a restricted location exempt from deletion. Confirm
the copies are accessible; changing lifecycle rules is not instantaneous. Resume
expiry and remove incident copies when recovery no longer needs them.
Monthly off-machine copy
Once a month, copy the backup from the first epoch that starts in that month (UTC)
to storage that none of your nodes can reach, since anyone who takes over a node
can delete what its credentials permit. Do not put it in the remote bucket that receives
automatic backups. Use that epoch's -mpc- archive, which holds the
node's recovery record. Recovery only combines
records from the same epoch, so every operator should copy the same epoch. Copy it
within 14 days, before local cleanup and cloud lifecycle rules remove it.
Test each copy when you make it, on a machine outside the validator fleet, by
exporting its recovery record with
hashi backup export-mpc-recovery-record to your
backup-pgp-cert, then delete the export. If that epoch has no -mpc- archive or the
export fails, keep your older copies and find out why. Do not copy a different epoch
instead.
Keep each copy for three months after its epoch, then destroy it everywhere it is
stored, including old versions, replicas and snapshots. Destroy expired copies only
after this month's copy passes its test, unless your node has left the committee.
Keep a way to decrypt every copy you still hold, even after you replace
backup-pgp-cert.
Restore from a backup
hashi backup restore decrypts an archive and extracts it into a directory you
choose. It installs nothing. The full procedure, from choosing a backup to
restarting the node, is in
Backup and Restore.
For remote restore commands and an on-page recovery checklist, see
Restore and infrastructure limits and
Machine-loss recovery below.
Remote backups
Configure one remote destination using a [backup-remote] table.
For S3, region is required:
[backup-remote]
destination = "s3://my-hashi-backups/"
region = "us-east-1"
For GCS, omit region:
[backup-remote]
destination = "gs://my-hashi-backups/"
Use a bucket-only URI; Hashi supplies the object prefix. Configure the bucket permissions, retention, and audit logging described below. Hashi does not set up or verify these controls.
S3 safeguards
Uploads and restores use the AWS default credential chain (environment, profiles, or IAM roles), not credentials in the node config. The secure Mainnet S3 baseline requires operators to configure:
- A private bucket with S3 Versioning and Object Lock with bucket-default retention; Hashi does not set retention per object.
- A bucket policy explicitly denying object-creation
s3:PutObjectrequests withoutIf-None-Match: *(s3:if-none-match), rather than relying on the client's header alone. - A node role restricted to
s3:PutObjectwithin its prefix, without object read or delete, bucket or retention administration, or governance-retention bypass. Restrict reads to approved recovery roles and keep their credentials off-node. - CloudTrail read and write object data events scoped to the backup bucket, retained in an independent audit store outside backup writers' permissions. Use per-node credentials for attribution: events identify the credential principal, not a trusted archive creator.
- A 14-day remote retention target, using lifecycle expiration:
- Expire current objects after 14 days (
Expiration.Days: 14). - Delete noncurrent versions after 1 day (
NoncurrentVersionExpiration.NoncurrentDays: 1), without retaining a minimum number of newer versions. - Remove expired delete markers with a separate rule
(
Expiration.ExpiredObjectDeleteMarker: true). Apply these rules to the backup prefix (or the whole dedicated backup bucket). Current expiry creates a delete marker; the data becomes noncurrent and is eligible for deletion 1 day later, not another 14 days later. Lifecycle uses S3 upload time, so late uploads need separate expiry tracking. Align bucket-default Object Lock retention with operator policy and verify it before locking. Object Lock can delay deletion, and lifecycle processing is asynchronous: this is not a hard deadline or a guaranteed recovery window.
- Expire current objects after 14 days (
GCS safeguards
GCS uses Application Default Credentials (ADC). Prefer an attached service account or workload identity. Keep recovery credentials on a separate trusted host, and never put credentials in node configuration.
- Bucket: enable uniform bucket-level access and public access prevention.
- Permissions: grant each node
roles/storage.objectCreatorwithin its backup prefix, without read, delete, or administrative access. Recovery identities needstorage.objects.get, plusstorage.objects.listto inspect history. - Retention lock: enable Object Versioning and Bucket Lock. Check the retention period first: locking is irreversible and the period cannot be reduced.
- Audit: enable Data Access logs
for
DATA_READandDATA_WRITE(disabled by default). Use per-node identities and retain logs outside their control. Lifecycle deletions are not logged. - Expiry: apply these lifecycle rules
to the backup prefix or dedicated bucket:
Deletewithage: 14andisLive: true.DeletewithdaysSinceNoncurrentTime: 1andisLive: false.
- Soft delete: account for its default 7-day retention, which extends storage beyond lifecycle deletion. To recover a soft-deleted object, restore it through GCS first, then use its new generation with Hashi.
Cloud age starts at upload, not archive creation; preserve the original expiry target when copying or recovering objects. Locks, holds, and asynchronous lifecycle processing can delay deletion.
Remote archive locations
Both providers use the same identity-derived namespace, with no custom prefix:
s3://<bucket>/<sui-chain-id>/<hashi-object-id>/<validator-address>/hashi-backup-epoch-<epoch>-<timestamp>.tar.asc
gs://<bucket>/<sui-chain-id>/<hashi-object-id>/<validator-address>/hashi-backup-epoch-<epoch>-<timestamp>.tar.asc
If the -mpc- marker is present after the epoch number, it identifies backups
containing MPC key-recovery information.
Failed uploads leave the local backup intact but are not retried automatically; check logs for failures.
Manual backups
Stop the node before opening its database for a manual backup:
hashi backup save /path/to/validator.toml --output-dir /var/lib/hashi/backups
Replace /path/to/validator.toml with the path to your node config, and
/var/lib/hashi/backups with the directory to write the archive to (for
example, your configured backup-dir).
This saves locally, then uploads to the configured remote destination.
Upload failure returns a nonzero exit status while preserving the local archive.
With a remote destination configured, a --backup-pgp-cert override with different usable encryption recipients requires --local-only or updating the configured certificate.
Restore and infrastructure limits
Restore from a full remote URI without the original node configuration. Use independently retained receipts to pin an S3 version or GCS generation:
hashi backup restore 's3://my-bucket/<namespace>/<archive>.tar.asc' \
--region us-east-1 --version-id '<version-id>' \
--backup-pgp-secret-key /secure/secret-key.asc \
--output-dir /private/restore
hashi backup restore 'gs://my-bucket/<namespace>/<archive>.tar.asc' \
--generation '<generation>' \
--backup-pgp-secret-key /secure/secret-key.asc \
--output-dir /private/restore
Replace the quoted placeholders with values from your backup receipts:
<namespace> is <sui-chain-id>/<hashi-object-id>/<validator-address>, and
<archive> is the full archive basename before .tar.asc. Keys are literal
object keys, not URL-decoded paths. S3 recovery credentials need s3:GetObject
for the current object or s3:GetObjectVersion for a pinned version;
inspecting history also requires s3:ListBucketVersions. GCS recovery requires
storage.objects.get, plus storage.objects.list to inspect history.
Remote restore accepts only encrypted .tar.asc archives. Choose
--backup-pgp-secret-key or --use-gpg-agent (with optional --gpg-homedir).
Local restores also accept plaintext .tar archives, which need neither decryptor.
--region is required for S3, and --version-id is S3-only. GCS rejects both
options and accepts --generation, a positive signed 64-bit integer.
S3 rejects --generation; all three remote options are invalid for local restores.
Downloads have no failed-request retry or resume.
Downloads use private temporary storage under --output-dir, and the command
removes them afterward. Restore publishes a complete extraction, refuses an existing
destination, and does not install files at their original paths.
Keep backup URI and S3 version ID or GCS generation receipts, the S3 region,
and decryption-key access outside the node. Use independently retained receipts
and audit history to select an exact version or generation, then pin it with
--version-id or --generation. Neither the newest object nor successful
decryption proves its origin: encryption does not authenticate the creator.
Unexpected versions, generations, or delete markers warrant investigation,
but lifecycle rules and authorized administrators can also change object history.
Machine-loss recovery
Use this path for accidental machine or disk loss without suspected compromise. For suspected compromise, use the separate procedure below, not a snapshot restore.
- Stop or fence the old node. Select a backup using independently kept receipts
and restore its pinned version or generation on a trusted replacement host
into a private directory (
umask 077); you need no original config or database. - Verify the restored chain, Hashi object, and validator identity against trusted
records. Install the config and key files at trusted paths, not paths taken
blindly from the manifest. Put
hashi-db-snapshotat the configureddbpath; never merge it into an existing database. - Update config paths and endpoints, restrict permissions to the node user, and start the node. Check chain catch-up and committee participation.
- Remove plaintext recovery output and temporary key copies when finished. Keep decryption-key access independent of the replacement host.
Suspected node compromise
Assume the host-held operator, TLS, and MPC private keys have leaked. Even a known pre-compromise snapshot can contain those same keys; its age does not make them safe to restore.
For a single compromised node with sufficient healthy peers:
- Fence the old host. From trusted infrastructure, use the validator account authority to revoke the old operator delegation and register fresh operator and TLS identities in onchain metadata. Follow the key generation and registration and operator revocation procedures with the replacement config; do not authorize recovery with the compromised operator key.
- Provision a fresh machine with an empty database, not restored key material. Startup generates fresh node encryption and signing keys and registers them; see server startup.
- Rejoin through reconfiguration with the healthy peers, and confirm the replacement identities and participation.
Suspected quorum-wide or global MPC key compromise requires coordinated incident response, not this single-node procedure. Resharing preserves the global Bitcoin key; it does not repair a stolen global private key.
Catastrophic MPC key recovery
A zero-day compromise could steal and delete MPC key shares across the validator fleet. Catastrophic data loss could also leave every node without its shares. Backups allow you to recover the existing MPC key when no functioning committee remains.
Scheduled backups with the -mpc- marker include hashi-mpc-recovery.bcs.
This file contains the node's shares and the information needed to combine them
with other nodes' backups to recover the MPC key. Recovery needs a sufficient set
of backups from the same epoch and works even after old onchain certificates are
gone. Manual backups do not include this file.
Operators should keep their monthly off-machine copies and keep access to their backup decryption keys separate from the validator fleet. MPC key recovery is an incident-response operation, not part of ordinary node restoration.
Recovery assembles the full MPC private key and writes it PGP-encrypted. You must still handle it as a highly sensitive secret. Recovering it does not invalidate a copy already stolen by an attacker.
Sharing recovery records
For a coordinated recovery, use hashi backup export-mpc-recovery-record to
decrypt your backup locally and export only its recovery information, encrypted
to the coordinator's public PGP certificate. The command prints the recipient
certificate's fingerprint; verify it through a trusted channel before sharing
the export.
hashi backup export-mpc-recovery-record backup.tar.asc \
--backup-pgp-secret-key /secure/operator-secret.asc \
--output-pgp-cert coordinator.asc --output recovery-record.bcs.asc
Use --use-gpg-agent instead of --backup-pgp-secret-key for agent-backed keys.
Send only the encrypted .bcs.asc file; you need to share neither your backup decryption key nor
the full backup. The command never writes a plaintext record.
The coordinator can pass these files to hashi backup recover-mpc-key and decrypt
them with their own key.