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.
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.
Guarded
Authenticated
Non-custodial
Distributed
Access
Local
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.
O
Factor 1 · password
Owner
Stores its strong recovery-factor password separately from the wallet backup JSON document.
Can combine with either custodianP
Factor 2 · HPKE
Partner
Holds a locally generated X25519 custodian key file with independent password protection.
Cannot inspect the wallet aloneC
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.
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.
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.
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.
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.
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.
WWallet payloadAccounts · secrets · networks
KRandom CEK32 bytes
⌁Encrypted onceAES-256-GCM
1Password + AES-GCM
2X25519 HPKE
3X25519 HPKE
{ }Backup JSONCiphertext + public policy
PasswordPartner keyCustodian key
2/3Two valid sharesInterpolate CEK
VFresh local vaultNew ID · salt · nonce · password
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.
Serialize locally. Portable wallet payload remains inside the core.
Encrypt once. A random CEK seals canonical payload bytes with AES-256-GCM.
Split the CEK. Bytewise GF(256) Shamir creates three shares with threshold two.
Protect each share. Password Argon2id/AES-GCM or RFC 9180 X25519 HPKE.
Distribute separately. Backup JSON and factor credentials travel independently.
Restore fresh. Two shares recover the CEK and reseal a new local vault.
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.
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.
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.
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.
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.
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.
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
Inspect
Strictly parse the envelope and show only its public manifest. No factor is opened.
2
Open distinct factors
Accept password, encrypted key-file, raw advanced key, or bound recovery-share credentials. Duplicates count once.
3
Recover and authenticate
Interpolate exactly M shares, decrypt canonical payload bytes, and validate scope and complete context coverage.
4
Re-derive identities
Derive every public key and address from its typed secret and immutable network identity. Any mismatch aborts all work.
5
Preview local mappings
Built-in identities remap automatically. Custom identities require explicit local connection/profile confirmation; endpoints are never restored.
6
Persist atomically
Reseal a fresh vault with new ID, time, password, salt, and nonce; commit accounts, contexts, settings, and cache removals together.
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
original backup + custodian key file + key password
│
▼
open one HPKE share (32 bytes)
│ never opens payload
▼
Argon2id + AES-GCM under a new share password
│
▼
bound recovery-share package
final recovery still needs: package + password + original backup
+ enough other factors
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.
Lowest coordination
Password 1-of-1
One password, one share
A familiar encrypted-backup flow expressed as threshold 1, share count 1. Security depends directly on password strength and storage separation.
1 / 1Password factor
Mixed recovery
Collaborative 2-of-3
One password, two custodians
The main story: the owner can recover with either custodian, while the two custodians can also help if the owner’s password is lost.
2 / 3Password + 2 HPKE
Institutional or bespoke
Custom M-of-N
Custom threshold governance
Teams can model role separation, continuity, and multi-site availability. The protocol does not prescribe people, institutions, or legal arrangements.
M / NUp to 16 factors in UI
Swipe or use the arrow keys to compare all three policies.
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.
Threshold is independent of factor type.Labels identify factors, never wallets or accounts.
Authentic UIGenerated custodian keyPrivate key exists only inside the password-encrypted file
Manifest inspection decrypts nothing.
Authentic UIImported public manifestFactor kinds and share indices are visible before credentials
Only this 32-byte share is rewrapped.
Authentic UICustodian share rewrappingThe original backup remains required for final recovery
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.
A
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.
V
Complete-vault scope
All portable accounts in the source vault and their covered immutable network contexts.
iFresh 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.
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.
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.
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.
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.