G.A.N.D.A.L.F. · gandalf-v1-draft · early draft

Multi-party backup. Local restore.

GANDALF — Guarded Authenticated Non-custodial Distributed Access for Local Failover — is an open, wallet-neutral draft for password, custodian, and M-of-N recovery. The wallet payload is encrypted once with a random content-encryption key; that key is split into independent threshold shares; each share is protected separately. Creation and restore are local operations—no recovery server participates in the cryptography.

  • One encrypted payload
  • Independent M-of-N factors
  • No network participant
Encrypted backup 2 of 3 Any two restore
OwnerPassword share
PartnerX25519 key
CustodianX25519 key

Explanatory identities and motion—not executable cryptography or real wallet data.

LocalCreate and restore in the wallet core
OpenJSON schemas and deterministic vectors
Threshold1-of-1 through custom M-of-N
NeutralNo Gandalf-specific server required

1 · Status of this memo

GANDALF: an early public draft.

This page is the human-readable guide to gandalf-v1-draft (G.A.N.D.A.L.F.). It is not an IETF Internet-Draft. The plain-text companion HTML specification (and downloadable gandalf-v1-draft.txt) is the RFC-style edition of the same draft.

  1. Guarded
  2. Authenticated
  3. Non-custodial
  4. Distributed
  5. Access
  6. Local
  7. Failover
Protocol
GANDALF (G.A.N.D.A.L.F.)
Draft ID
gandalf-v1-draft
Status
Early draft
Media type
application/json
Wire profile
wallet-backup · v1 (provisional)
Published

Abstract

GANDALF means Guarded Authenticated Non-custodial Distributed Access for Local Failover. This draft specifies an open, wallet-neutral encrypted backup family for password, custodian, and M-of-N wallet recovery. A wallet payload is encrypted once with a uniformly random 32-byte content-encryption key (CEK). That CEK is divided into threshold shares using bytewise Shamir secret sharing over GF(256). Every share is then protected independently by either a normalized password (Argon2id + AES-256-GCM) or an X25519 recipient under RFC 9180 HPKE base mode.

Mixed factor types are permitted in a single policy. A password-only backup is the 1-of-1 case. Backup files may be copied or stored by untrusted systems; factor credentials travel separately. Creation and recovery are entirely local. No server, proxy, cloud service, analytics system, or other network participant is part of the cryptographic protocol.

This is an early draft. Implementers SHOULD expect incompatible revisions before a frozen profile is declared. The Gandalf Wallet reference implementation currently serializes the first concrete wire profile using the provisional discriminator wallet-backup and related schema identifiers.

Requirements language

The key words MUST, MUST NOT, REQUIRED, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in BCP 14 [RFC 2119] [RFC 8174] when, and only when, they appear in all capitals, as shown here.

2 · Introduction

Three independent factors. No single point of recovery.

The protocol separates an encrypted wallet payload from the credentials needed to open it. The following 2-of-3 policy is illustrative, not mandatory: it shows how mixed factor types cooperate without giving any single party—or the storage provider—enough material to recover alone.

An owner keeps a strong recovery-factor password separately from the backup JSON document. Their partner and an independent custodian each hold a password-encrypted X25519 key file. Any two of those three factors, presented to a conforming local client together with the original backup envelope, can reconstruct the CEK and restore a fresh vault. One factor alone cannot. The storage medium that holds the backup JSON is never a cryptographic participant.

Factor 1 · password

Owner

Stores its strong recovery-factor password separately from the wallet backup JSON document.

Can combine with either custodian

Factor 2 · HPKE

Partner

Holds a locally generated X25519 custodian key file with independent password protection.

Cannot inspect the wallet alone

Factor 3 · HPKE

Independent custodian

Holds a separate key and returns one password-wrapped recovery-share package when needed.

Never receives another share
Separate the things that must come together. Store factor credentials apart from the backup JSON and distribute them across geographic and organizational boundaries appropriate to your own risks. This example is illustrative, not legal or custody advice.

3 · Backup creation

One payload becomes a recoverable policy.

Backup creation is a local producer procedure. A conforming implementation serializes only portable material, encrypts that payload once, splits the CEK into shares, protects each share independently, and emits a public JSON envelope. This page never performs the cryptography; the animation is explanatory structure only.

Normative producer steps

The following procedure is the normative backup-creation flow. Steps are ordered. Secret material remains inside the native core (Rust in the Gandalf reference implementation) and is zeroized as soon as practical after use.

  1. B1

    Select scope and serialize the portable payload

    Choose account scope (exactly one account and its selected immutable network context) or complete-vault scope (all portable accounts and their covered contexts). Serialize only identity, typed secrets, derivation metadata, and immutable network identity. Balances, UTXOs, history, fees, endpoints, and caches MUST NOT be included. Encode the logical record with RFC 8785 JSON Canonicalization Scheme (JCS) so authenticated bytes are cross-language exact.

  2. B2

    Generate a content-encryption key and seal the payload once

    Draw a uniformly random 32-byte CEK and a random 12-byte nonce from a cryptographically secure source. Seal the canonical payload bytes with AES-256-GCM using the payload AAD domain wallet-backup-v1/payload (ASCII domain, one zero byte, then the JCS object that binds format, version, algorithms, backup identity, threshold, share count, and factor descriptors). Compute payload.digest as SHA-256 over the decoded ciphertext including the 16-byte GCM tag; the nonce is not included in the digest.

  3. B3

    Split the CEK with bytewise Shamir secret sharing

    For each of the 32 CEK bytes independently, construct a polynomial of degree M−1 over GF(256) with the CEK byte as the constant term and M−1 uniform random higher coefficients. Evaluate at each public share index (distinct integers 1–255) using addition as XOR and multiplication modulo the irreducible polynomial x⁸+x⁴+x³+x+1 (0x11b). Interactive clients MUST cap N at 16 even though the wire algorithm supports 255. Coefficients and intermediate share material MUST be zeroized promptly after use.

  4. B4

    Protect every share independently

    Each 32-byte share is sealed under its own factor. A password factor derives a key with Argon2id v1.3 (65,536 KiB memory, 3 iterations, parallelism 1, unique 16-byte salt, 32-byte output) and seals the share with AES-256-GCM. An HPKE factor uses RFC 9180 base mode—DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-256-GCM— to seal the share to a recipient public key. Share AAD and HPKE info bind backupId, payload digest, threshold, share index, and factor identity so a share cannot be moved between factors, backups, or ciphertexts.

  5. B5

    Emit the public envelope and distribute credentials separately

    Write ordinary JSON with the public policy, protected shares, and encrypted payload. Factor labels identify factors only and MUST NOT contain wallet or account names. Store the backup JSON apart from passwords and custodian key files. Geographic and organizational separation of factors is an operational choice outside the wire format; the protocol only requires that a valid threshold of independent credentials be required for restore.

What backup creation produces. One portable backup document (ciphertext + public policy) plus independently held factor credentials. Successful creation does not create a recovery service, cloud account, or network callback. Failed creation MUST NOT leave partial secret state on disk.
Step 1 of 6

The wallet serializes only portable account, secret, and immutable network data inside the Rust core.

Use Left/Right to step and Space to pause or play.

  1. Serialize locally. Portable wallet payload remains inside the core.
  2. Encrypt once. A random CEK seals canonical payload bytes with AES-256-GCM.
  3. Split the CEK. Bytewise GF(256) Shamir creates three shares with threshold two.
  4. Protect each share. Password Argon2id/AES-GCM or RFC 9180 X25519 HPKE.
  5. Distribute separately. Backup JSON and factor credentials travel independently.
  6. Restore fresh. Two shares recover the CEK and reseal a new local vault.
Diagram A End-to-end backup pipeline

The canonical portable payload is encrypted once using AES-256-GCM and a random content-encryption key. Shamir splits that key into three shares. One share is password protected and two use X25519 HPKE. The backup JSON and credentials are stored separately.

After distribution, restore is the inverse consumer procedure: open enough independent factors, interpolate the CEK, authenticate and decrypt the payload, re-derive every public identity, and commit a fresh local vault. That flow is specified next.

4 · Restore procedure

Validate, derive, review, then commit once.

Successful decryption is not enough. A conforming client re-derives every declared public identity, maps immutable networks, resolves local duplicates, and creates a fresh normal vault in one atomic state transition. Failed restore MUST NOT partially persist accounts, contexts, settings, or caches.

Normative consumer steps

Restore is one atomic flow. Manifest inspection decrypts nothing. Plaintext secrets and shares MUST NOT cross a WASM or UniFFI boundary; only encrypted files, public descriptors, and encrypted replacement vaults may leave the core.

  1. R1

    Strictly parse the envelope

    Validate format and version, the mandatory algorithm suite, size limits (document ≤ 16 MiB; decrypted payload ≤ 8 MiB), identifiers, threshold and share-count consistency, ciphertext lengths, unique salts and share indices, and the payload digest. Reject non-I-JSON values, floating-point protocol numbers, duplicate members or protocol IDs, unknown object members, zero indices, empty IDs, malformed or non-contributory keys, and unknown mandatory algorithms.

  2. R2

    Inspect the public manifest without decrypting

    Show only deliberately public fields: algorithms, backup identity, threshold policy, factor IDs and labels, share indices, factor kinds, X25519 fingerprints, and cipher parameters. Inspection MUST NOT open any factor or decrypt the payload. Operators use this step to confirm policy shape before supplying credentials.

  3. R3

    Open distinct factors until threshold is met

    Accept password credentials, password-encrypted custodian key files, advanced raw X25519 private keys (exactly 32 bytes or 64 hex digits), or bound recovery-share packages. Duplicate credentials count once. Invalid extra credentials MAY be ignored if at least M valid, distinct shares remain. Password fields and private keys are cleared after every attempt, successful or not.

  4. R4

    Interpolate the CEK and authenticate the payload

    Using Lagrange interpolation at x = 0 over GF(256), reconstruct the CEK from exactly M valid shares. Decrypt with the payload AAD, require canonical payload bytes, and validate scope and complete context coverage. Every recovered account MUST have at least one immutable network context.

  5. R5

    Re-derive identities and preview local mappings

    Derive every declared public key and address from its typed secret, derivation metadata, and immutable network identity. Any mismatch aborts the entire operation. Built-in network identities remap automatically by immutable identity (ledger, numeric networkId, genesisId). Custom identities require explicit local profile or connection confirmation; provider endpoints are never restored from the backup.

  6. R6

    Persist a fresh vault atomically

    Create a normal local vault with a new vault ID, creation time, password, salt, and nonce. Source vault ID and dates are provenance only; recovery does not reinstate the source vault envelope. Commit accounts, network contexts, settings, and cache removals together. Legacy raw Gandalf VaultEnvelope files are not wallet-backup documents and MUST NOT be imported by this flow.

  1. 1

    Inspect

    Strictly parse the envelope and show only its public manifest. No factor is opened.

  2. 2

    Open distinct factors

    Accept password, encrypted key-file, raw advanced key, or bound recovery-share credentials. Duplicates count once.

  3. 3

    Recover and authenticate

    Interpolate exactly M shares, decrypt canonical payload bytes, and validate scope and complete context coverage.

  4. 4

    Re-derive identities

    Derive every public key and address from its typed secret and immutable network identity. Any mismatch aborts all work.

  5. 5

    Preview local mappings

    Built-in identities remap automatically. Custom identities require explicit local connection/profile confirmation; endpoints are never restored.

  6. 6

    Persist atomically

    Reseal a fresh vault with new ID, time, password, salt, and nonce; commit accounts, contexts, settings, and cache removals together.

Diagram B Direct threshold recovery

The original backup, owner password, and partner key open two distinct shares. Those shares interpolate the content key, which authenticates and decrypts the payload before creating a fresh encrypted local vault.

Diagram C Custodian recovery-share rewrapping

A custodian opens one HPKE share using the original backup and encrypted key file. Only that share is re-encrypted under a new password. Final recovery still needs the package, password, original backup, and enough other factors.

Custodian recovery-share packages

A custodian may open one original HPKE factor and re-encrypt only that 32-byte share under a new password, producing a wallet-recovery-share-v1 package. Rewrapping never opens the wallet payload. Final recovery always requires the original backup envelope; an implementation MUST reject a package whose binding differs byte for byte from the original factor binding (backupId, payload digest, threshold, share index, factor ID).

Duplicate-account resolution

When a restored account ID already exists locally, exact identities may remain while missing contexts or settings are added. Conflicting identities require typed confirmation and the current passwords of affected local vaults. Replacement removes only collisions, preserves unrelated accounts, clears reconstructed caches for colliding accounts, and deletes an empty superseded vault only after the transaction succeeds.

Duplicate-account resolution

Preserve exact identities. Confirm conflicts.

Exact identityMay remain in its current vault while missing contexts or settings are added.

Conflicting identityRequires typed confirmation and current passwords for affected local vaults.

Atomic replacementRemoves only collisions, preserves unrelated accounts, clears reconstructed caches, and deletes an empty old vault only after success.

5 · Recovery policies

Use the factor shape your recovery needs.

Version 1 expresses policy as a single threshold over independent factors—not as a product feature list. Interactive clients allow one to sixteen factors, mixed freely within one policy. The wire algorithm supports share indices through 255.

6 · Reference client

Gandalf Wallet backup recovery

The following frames are deterministic captures of the locally built browser extension— authentic UI evidence of the reference client, not redesigned mockups. Fictional labels and test-only secrets are used throughout. Captures illustrate the procedure; they do not execute cryptography in the browser image.

Gandalf extension Backup screen configured with an advanced two-of-three policy using one password and two X25519 factors Threshold is independent of factor type. Labels identify factors, never wallets or accounts.
Authentic UIAdvanced mixed-factor backupComplete-vault scope · 2-of-3 · fictional factor labels
Gandalf extension Key screen after generating an encrypted X25519 custodian key, showing the public key and fingerprint Compare this fingerprint out of band.
Authentic UIGenerated custodian keyPrivate key exists only inside the password-encrypted file
Gandalf extension Restore screen showing an imported two-of-three backup manifest and its three public factor descriptors Manifest inspection decrypts nothing.
Authentic UIImported public manifestFactor kinds and share indices are visible before credentials
Gandalf extension Share screen after exporting a password-encrypted recovery-share package for one HPKE factor Only this 32-byte share is rewrapped.
Authentic UICustodian share rewrappingThe original backup remains required for final recovery
Gandalf extension Restore preview showing recovered fictional accounts and immutable network mappings before atomic restore Network identity is immutable; profiles are local mappings. One save or no changes.
Authentic UIAccount and network previewFresh-vault restore is reviewed before atomic persistence

Capture provenance and reproduction commands are documented in PROVENANCE.md. Captures are evidence of the reference interface, not proof of cryptographic execution in an image.

7 · Portable scope

Enough to recover identity. Nothing stale to restore.

A backup carries either one selected account context or a complete vault. In both cases the encrypted payload holds portable identity and secret material—never balances, history, or other observations that a restored client can resynchronize from the network.

The plaintext payload is the RFC 8785 encoding of a logical record with scope VAULT or ACCOUNT(accountId, contextId), source-vault provenance, an accounts[] array of metadata plus typed secret, and a networkContexts[] array identifying each ledger by ledger, numeric networkId, and immutable genesisId. Secret types in version 1 are Tetra entropy, validator private key, BIP-39 entropy, and Bitcoin WIF. New secret types require a new protocol version or an explicitly negotiated extension.

Account scope

Exactly one account and its selected immutable network context.

BIP-39 caveat Clients MUST warn that recovered shared BIP-39 entropy may derive sibling accounts even when their metadata is not restored.

Complete-vault scope

All portable accounts in the source vault and their covered immutable network contexts.

Fresh local vault The source vault ID and dates describe provenance only; recovery creates new local sealing metadata.

Encrypted inside

Portable identity and secrets

  • Wallet and account names, IDs, public keys, and addresses
  • Typed secret sources and derivation/address metadata
  • Ledger, network ID, immutable genesis identity, aliases, and finality
  • Built-in profile hints for safe local remapping

Never included

Remote or reconstructible state

  • Balances and UTXOs
  • Transaction history and pending transactions
  • Fees, provider responses, indexes, and sync timestamps
  • Explorer, proxy, or provider endpoints and all caches

8 · Wire format

Public policy around an opaque wallet.

A backup is ordinary JSON with strict members and limits. The public envelope reveals how recovery is organized—threshold, factor kinds, fingerprints, cipher parameters—never which wallet or account it protects. Wallet names, account identifiers, public keys, addresses, secrets, and network contexts exist only inside the encrypted payload.

Deliberately public

Envelope and policy

  • Format, version, algorithms
  • Backup ID and creation time
  • Threshold and share count
  • Factor IDs, neutral labels, indices, kinds
  • X25519 fingerprints, nonces, ciphertexts, digest

AES-256-GCM encrypted

Canonical payload

  • Wallet and source-vault details
  • Account metadata and typed secrets
  • Public keys, addresses, derivation data
  • Ledgers and immutable network contexts
  • Aliases and portable finality settings
Safe public shapeAbbreviated envelope
{
  "format": "wallet-backup",
  "version": 1,
  "backupId": "example-not-a-real-backup",
  "createdAt": "2026-07-21T12:00:00Z",
  "recoveryPolicy": {
    "threshold": 2,
    "shareCount": 3,
    "factors": [
      { "factorId": "owner", "label": "Owner password", "shareIndex": 1,
        "protection": { "kind": "PASSWORD", "kdf": "…", "cipher": "…" } },
      { "factorId": "partner", "label": "Partner", "shareIndex": 2,
        "protection": { "kind": "HPKE", "recipientFingerprint": "sha256:…" } },
      { "factorId": "custodian", "label": "Independent custodian", "shareIndex": 3,
        "protection": { "kind": "HPKE", "recipientFingerprint": "sha256:…" } }
    ]
  },
  "payload": { "cipher": { "algorithm": "AES-256-GCM", "nonce": "…", "ciphertext": "…" },
               "digest": "sha256:…" }
}

Illustrative and intentionally incomplete. It is not a valid backup or a conformance vector.

Strict parser requirementsLimits, duplicates, unknown members, and binding checks

Conforming implementations reject documents over 16 MiB, decrypted payloads over 8 MiB, non-I-JSON and floating-point values, duplicate members or protocol IDs, invalid thresholds or indices, malformed and non-contributory keys, unknown mandatory algorithms, payload digest mismatches, and any factor or package bound to another backup.

Unknown object members are rejected. Complete producers should emit RFC 8785 canonical JSON, while consumers authenticate the specified canonical projections.

GANDALF gandalf-v1-draft encoding rules (provisional wire profile)
ValueEncoding or limitReason
Binary fieldsPadded standard Base64Unambiguous RFC 4648 transport
Digestssha256: + 64 lowercase hex digitsTyped human inspection
Passwords and labelsUTF-8 after Unicode NFCStable derivation and AAD
Password lengthAt least 12 Unicode scalar values after NFCMinimum only; strength still matters
Share indexDistinct integer 1–255Zero is interpolation target
Interactive N1–16 factorsWire construction supports 255

Wire vs public name: the public draft is gandalf-v1-draft (GANDALF). The first concrete wire profile still uses provisional wallet-backup labels and https://gandalfwallet.com/protocol/… schema $id values in the reference implementation.

9 · Mandatory algorithm suite

One exact suite, with every layer bound.

Version 1 does not negotiate algorithms. Names are case-sensitive, parameters are fixed, and unknown mandatory values fail closed. The algorithms object on the wire MUST equal the frozen value published in Appendix A of the text specification.

01

AES-256-GCM

Payload and password shares use a 12-byte nonce and 16-byte authentication tag. Nonce uniqueness is required per key; random 96-bit nonces are generated independently.

02

Argon2id v1.3

Exactly 65,536 KiB memory, 3 iterations, parallelism 1, unique 16-byte salt per protected object, and 32-byte output. Other parameters fail closed.

03

X25519 HPKE

RFC 9180 base mode only: DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, and AES-256-GCM. Encapsulated keys are the RFC 9180 enc output.

04

Shamir GF(256)

Bytewise sharing over x⁸+x⁴+x³+x+1 (0x11b). Recovery uses Lagrange interpolation at x = 0.

05

RFC 8785 JCS

Canonical JSON makes authenticated metadata and deterministic cross-language vectors byte-exact. Producers should emit complete documents as JCS.

06

SHA-256

Fingerprints identify raw 32-byte X25519 public keys as sha256: plus 64 lowercase hex digits. Digests bind the decoded payload ciphertext including the GCM tag.

Frozen suiteMandatory algorithms object
{
  "payloadCipher": "AES-256-GCM",
  "passwordKdf": "ARGON2ID-1.3",
  "passwordCipher": "AES-256-GCM",
  "secretSharing": "SHAMIR-GF256-BYTEWISE",
  "hpke": "DHKEM(X25519,HKDF-SHA256)/HKDF-SHA256/AES-256-GCM",
  "canonicalJson": "RFC8785"
}

Normative exact value from the frozen specification. Case-sensitive; unknown values MUST be rejected.

Domain-separated AAD

Same fields, different cryptographic purpose.

Each AAD value is an ASCII domain, one zero byte, then RFC 8785 bytes of its exact object. A share cannot be moved between factors, indices, thresholds, backups, or ciphertexts.

wallet-backup-v1/payload
Suite, backup identity, threshold, factor descriptors
wallet-backup-v1/share
Binding + factor descriptor
wallet-backup-v1/hpke-info
Same binding + factor descriptor
wallet-backup-v1/custodian-key
Key file metadata and public identity
wallet-backup-v1/recovery-share
Complete original factor binding
Zeroization and secret lifetimeWhat implementations clear and what never crosses an adapter

CEKs, Argon2 keys, private custodian keys, decrypted payload bytes, wallet secrets, Shamir coefficients, and shares are zeroized as soon as practical and never logged. Password and private-key fields are cleared after every attempt. Plaintext secrets and shares never cross a WASM or UniFFI boundary.

10 · Threat model

Encrypted against storage. Honest about endpoints and availability.

The protocol protects wallet metadata and secrets against disclosure and undetected modification when backup files are copied, stored by an untrusted cloud, or held by a custodian. It is not a remote recovery service, an availability guarantee, or a substitute for a trustworthy creating or restoring endpoint.

Confidentiality fails if an attacker obtains any valid threshold of factors, guesses enough weak passwords offline, compromises the creating or restoring endpoint, or replaces the wallet software. Availability is not guaranteed: factor loss, malicious deletion, or denial of service can make recovery impossible. Argon2id increases offline guess cost; it cannot make a weak password strong.

Designed to protect

Untrusted storage or custody

  • Backup JSON copied or leaked without enough factors
  • One custodian or participant acting alone
  • Metadata, ciphertext, or share substitution
  • Malformed, oversized, ambiguous, or cross-bound documents

Limits and failure modes

Threshold security is conditional

  • Any valid threshold of factors can recover
  • Weak passwords can be guessed offline
  • Compromised creating/restoring endpoints can expose plaintext
  • Lost factors, deletion, or denial of service can make recovery impossible

Not in the protocol

There is no Gandalf server, cloud, proxy, analytics call, or recovery operator.

Backup creation, factor opening, threshold reconstruction, payload validation, and vault resealing happen locally. A storage provider can hold ciphertext bytes; it never becomes a cryptographic participant in CEK reconstruction.

Diagram D Trust boundaries and data that never leaves the Rust core

The UI selects files, accepts credentials, and shows public review data. The Rust core alone handles plaintext secrets, keys, shares, derivation, resealing, and zeroization. WASM and UniFFI return encrypted files, public descriptors, or encrypted vaults. Untrusted storage receives only encrypted bytes.

This is the specified threat model, not a claim of an independent security audit or a guarantee that recovery will succeed.

11 · Conformance contract

Schemas define shape. Vectors define bytes.

Passing the published version 1 vectors is the interoperability contract. Native Rust, browser WASM, and mobile UniFFI MUST consume and produce the same canonical bytes. Vector-only randomness is an incrementing byte stream from a published seed; production implementations MUST use a cryptographically secure random source.

Rust coreNormative implementation
WASMBrowser extension adapter
UniFFINative mobile adapter