Abstract
This document specifies GANDALF (Guarded Authenticated Non-custodial Distributed Access for Local Failover), an open, wallet-neutral encrypted backup family for password, custodian, and M-of-N wallet recovery. A wallet payload is encrypted once with a random content- encryption key. That key is divided into threshold shares, and every share is protected independently by either a password or an X25519 recipient.
Creation and recovery are local operations. No server, proxy, cloud service, analytics system, or other network participant is part of the cryptographic protocol.
Status of This Memo
This is the RFC-style text edition of the gandalf-v1-draft specification. It is not an IETF Internet-Draft and has not been submitted to the IETF. The draft is experimental: implementers SHOULD expect incompatible revisions before a frozen profile is declared.
The Gandalf Wallet reference implementation currently serializes the first concrete wire profile of this draft using the provisional discriminator "wallet-backup" and schema identifiers under gandalfwallet.com. Those identifiers are provisional wire labels for the gandalf-v1-draft profile; a later revision MAY rename them to align with the GANDALF public name without changing the security goals.
Implementations claiming compatibility with this draft MUST document which profile revision and vectors they consume.
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.
Table of Contents
1. Introduction
GANDALF (gandalf-v1-draft) is an encrypted backup family that separates an encrypted wallet payload from the credentials needed to recover it. The payload is encrypted exactly once with a uniformly random 32-byte content-encryption key (CEK). The CEK is divided into M-of-N bytewise Shamir shares. Each share is then protected independently by either a normalized password or an X25519 recipient. Mixed factor types are permitted. A password backup is the 1-of-1 case.
- Media type
- application/json
- Protocol name
- GANDALF (G.A.N.D.A.L.F.)
- Draft identifier
- gandalf-v1-draft
- Wire discriminator
- wallet-backup (provisional profile label)
- Wire version
- 1
1.1. Goals
The protocol permits wallet software to express password-only, collaborative, and institutional recovery policies without relying on a wallet-specific recovery server. Backup files may be copied or stored by untrusted systems. Factor credentials are distributed separately from the backup envelope.
1.2. Threat Model
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.
Confidentiality fails if an attacker obtains any valid threshold of factors, guesses enough weak passwords, 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.
Backup creation and recovery are entirely local. No server, proxy, cloud service, analytics call, or other network participant is part of the cryptographic protocol.
2. Mandatory Algorithm Suite
Version 1 has one mandatory suite. Algorithm names are case-sensitive. Unknown values MUST be rejected.
| Purpose | Mandatory algorithm |
|---|---|
| Payload and password-share | AES-256-GCM, 12-byte nonce, |
| authenticated encryption | 16-byte tag |
| Password KDF | Argon2id v1.3, 65,536 KiB, 3 iterations, parallelism 1, 32-byte output |
| Password salt | Unique random 16 bytes for each protected object |
| Public-key factor | RFC 9180 base mode, DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, AES-256-GCM |
| Secret sharing | Bytewise Shamir over GF(256), polynomial x^8+x^4+x^3+x+1 |
| Hashes and fingerprints | SHA-256 |
| Authenticated JSON | RFC 8785 JSON Canonicalization Scheme (JCS) |
| Text and password | UTF-8 after Unicode NFC |
| normalization | normalization |
Passwords MUST contain at least 12 Unicode scalar values after NFC normalization. Share indices are distinct integers from 1 through 255. Interactive version 1 clients MUST cap N at 16 even though the wire algorithm supports 255.
3. Encoding, Parsing, and Limits
Binary fields use padded standard Base64 as specified by Section 4 of [RFC 4648]. SHA-256 values use the form "sha256:" followed by exactly 64 lowercase hexadecimal digits. X25519 public and private keys are raw 32-byte values. An advanced raw private-key user interface MAY also accept exactly 64 hexadecimal digits.
Implementations MUST reject all of the following:
- documents larger than 16 MiB or decrypted payloads larger than 8 MiB;
- non-I-JSON values, floating-point protocol values, and integers outside the exact I-JSON range;
- duplicate JSON members, factor identifiers, share indices, account identifiers, or context identifiers;
- zero indices, empty identifiers, invalid thresholds, malformed keys, non-contributory X25519 inputs, unknown mandatory algorithms, and KDF parameters other than the exact version 1 values; and
- payload digest mismatches, authenticated metadata changes, and shares or packages bound to another backup, ciphertext, threshold, index, or factor.
Parsers MUST reject unknown object members. Producers SHOULD serialize the complete document as JCS. Consumers authenticate the specified JCS projections and MAY accept insignificant wire whitespace or member ordering.
4. Public Backup Envelope
The normative envelope schema is wallet-backup-v1.schema.json.
Only the following values are deliberately public:
- format and version;
- algorithm identifiers;
- backup identifier and creation time;
- threshold policy;
- factor identifiers and labels;
- share indices and factor kinds;
- X25519 fingerprints; and
- cipher parameters and ciphertexts.
A factor label is a human-readable factor identifier and MUST NOT contain a wallet or account name.
Wallet names, source vault identifiers, account names and aliases, account identifiers, public keys, addresses, derivation data, secret sources, ledgers, genesis identities, and network contexts occur only in the encrypted payload.
payload.digest is SHA-256 over the decoded payload ciphertext, including the GCM authentication tag. The nonce is not included.
5. Encrypted Payload
The plaintext payload is the RFC 8785 encoding of this logical record:
scope = VAULT | ACCOUNT(accountId, contextId)
sourceVaultId, sourceCreatedAt, sourceNetworkId
accounts[] = { metadata, secret }
networkContexts[] = {
contextId, accountId, ledger, networkId, genesisId,
builtinProfileId?, alias?, finalityThreshold?
}
metadata is the portable account identity. It includes the account identifier and name, typed account kind, account index, public key, primary address, derivation and address scheme, ledger, and every known derived address with its address-specific metadata.
secret is typed as Tetra entropy, validator private key, BIP-39 entropy, or Bitcoin WIF. New secret types require a new protocol version or an explicitly negotiated algorithm extension.
5.1. Account and Vault Scope
Every recovered account MUST have at least one immutable context.
An account-scope payload contains exactly one account and exactly its selected context. If its secret is shared BIP-39 entropy, clients MUST warn that the entropy can derive sibling accounts even though their metadata is not restored.
A vault-scope payload contains the selected vault's portable accounts and their required network contexts. Recovery always creates a fresh normal local vault; it does not restore the source vault envelope.
5.2. Network Identity and Excluded Data
A context identifies a ledger by ledger, numeric networkId, and immutable genesisId. builtinProfileId is only a remapping hint. Provider endpoints are never included.
Balances, UTXOs, transaction history, pending or provisional transactions, fee and provider responses, indexes, synchronization timestamps, explorer and provider endpoints, and all other caches MUST NOT be included. Restored clients reconstruct those values by synchronization.
6. Canonical Authenticated Data
Authenticated additional data (AAD) is the ASCII domain, followed by one zero byte, followed by the RFC 8785 bytes of the stated object.
6.1. Payload Protection
The payload domain is:
wallet-backup-v1/payload
The payload AAD object is:
{
format, version, algorithms, backupId, createdAt,
threshold, shareCount,
factors: [{
factorId, label, shareIndex, kind,
recipientFingerprint?
}]
}
The producer generates a random 32-byte CEK and a random 12-byte nonce, then seals the canonical payload using AES-256-GCM and this AAD.
6.2. Protected Shares
The share binding is:
{
backupId, payloadDigest, threshold, shareIndex, factorId
}
The share domain is:
wallet-backup-v1/share
Its AAD object is { binding, factor }, where factor is the same descriptor used by the payload AAD.
For a password factor, the producer derives a key using the factor's KDF object and seals the 32 share bytes with AES-256-GCM.
For an HPKE factor, info is the domain "wallet-backup-v1/hpke-info" applied to the same { binding, factor }. HPKE seals the 32 share bytes with the share AAD. encapsulatedKey is the RFC 9180 enc output.
7. Shamir Construction
For each CEK byte independently, choose M-1 uniformly random coefficient bytes. The CEK byte is the constant term. Evaluate the polynomial at each public share index using GF(256) addition (XOR) and multiplication modulo 0x11b. Recovery uses Lagrange interpolation at x=0.
Coefficients, shares, and the reconstructed CEK MUST be zeroized promptly.
8. Recovery Procedure
A conforming implementation performs recovery as one atomic flow:
- Strictly parse and validate the envelope, mandatory suite, limits, identifiers, threshold, ciphertext lengths, unique salts and indices, and payload digest.
- Inspect the manifest without decrypting any content. Manifest inspection MUST NOT decrypt anything.
- Open supplied password factors, HPKE factors, or bound recovery-share packages. Duplicate credentials do not count twice. Invalid extra credentials MAY be ignored if at least M valid, distinct shares remain.
- Interpolate exactly M valid shares, decrypt the payload using the payload AAD, require canonical payload bytes, and validate its scope and context coverage.
- Re-derive every declared public key and address from its typed secret, derivation metadata, and network identity. Any mismatch aborts the entire operation.
- Create a fresh normal local vault with a new vault identifier, creation time, password, salt, and nonce. Plaintext secrets and shares MUST NOT cross a WASM or FFI boundary.
Legacy raw Gandalf VaultEnvelope files are not wallet-backup documents and MUST NOT be imported by this flow.
8.1. Network Remapping and Duplicate Accounts
Built-in network identities remap automatically by immutable identity. A custom identity requires explicit profile or connection confirmation. Endpoints are never silently restored.
Duplicate local account identifiers require an atomic review. Exact identities may remain while missing contexts and settings are added. Conflicting identities require typed confirmation plus the affected current vault passwords.
Replacement removes only colliding accounts, preserves unrelated accounts, clears the colliding accounts' synchronization, history, and pending caches, and deletes an empty superseded vault only after the transaction succeeds.
9. Custodian Key Files
The normative schema is wallet-custodian-key-v1.schema.json.
The public key and its fingerprint are visible. The raw 32-byte private key is encrypted with the mandatory password KDF and AES-256-GCM.
The AAD domain is:
wallet-backup-v1/custodian-key
Its object contains format, version, algorithms, publicKey, fingerprint, kdf, cipherAlgorithm, and nonce. When opening a key file, implementations MUST derive the public key again and compare both the public key and fingerprint fields.
10. Recovery-Share Packages
The normative schema is wallet-recovery-share-v1.schema.json.
A custodian may open one original HPKE factor and re-encrypt only its 32-byte share with a new password.
The AAD domain is:
wallet-backup-v1/recovery-share
Its object contains format, version, algorithms, the complete binding, kdf, cipherAlgorithm, and nonce.
Final recovery always requires the original backup. An implementation MUST reject a package whose binding differs byte for byte from the original factor binding.
11. Conformance Vectors
wallet-backup-v1.json contains deterministic, non-secret vectors for password-only, HPKE-only, mixed-factor, account-scope, vault-scope, and recovery-share-package cases.
Vector-only randomness is an incrementing byte stream beginning at the published seed. Production implementations MUST use a cryptographically secure random source.
Native Rust, WASM, and UniFFI implementations MUST produce and consume the vector bytes exactly. Passing the published draft vectors is the current interoperability contract for the provisional wallet-backup wire profile of gandalf-v1-draft.
12. Security Considerations
Implementations MUST observe all of the following requirements:
- Password and private-key fields are cleared after every attempt, whether successful or not.
- CEKs, Argon2 keys, private custodian keys, decrypted payload bytes, vault secrets, Shamir coefficients, and shares are zeroized as soon as practical and are never logged.
- Failed recovery does not partially persist accounts, contexts, settings, or caches.
- AES-GCM nonce uniqueness is REQUIRED per key. Random 96-bit nonces are generated independently.
- Recipients compare displayed X25519 fingerprints before export. Duplicate public keys are rejected.
- User-interface errors use the client's noninteractive error toast while leaving a retryable flow intact.
Password strength remains part of the security boundary. An attacker with an encrypted password factor can attempt guesses offline. The mandatory Argon2id parameters increase guess cost but cannot make a weak password strong.
Threshold protection does not protect a compromised creating or restoring endpoint. It also does not guarantee availability after factor loss or deletion. Users SHOULD keep factors geographically and organizationally separate where their recovery policy requires independence.
13. Gandalf Reference Implementation Boundary
gandalf-core is the sole owner of plaintext backup material. Its WASM and UniFFI adapters expose operations to:
- generate encrypted custodian keys;
- inspect manifests;
- create backups;
- open one factor for validation;
- rewrap one HPKE share;
- restore a threshold directly into a fresh VaultEnvelope; and
- reconcile duplicate accounts by resealing affected vaults.
Factor-open calls return only the public factor descriptor. CEKs, shares, decrypted payloads, vault secrets, and private custodian keys never cross either boundary.
Duplicate reconciliation accepts exact and conflicting decisions, plus current passwords only for local vaults containing conflicts. It returns encrypted replacement envelopes and identifiers of now-empty vaults. Clients validate the result against current state and commit vaults, contexts, settings, and cache removals in one state transition.
14. IANA Considerations
This document has no IANA actions. "application/json" is used as the media type; "wallet-backup" and version 1 are fields within the JSON format and are not IANA registrations.
15. References
15.1. Normative References
- [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
- [RFC 4648] Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, October 2006.
- [RFC 8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, May 2017.
- [RFC 8785] Rundgren, A., Jordan, B., and S. Erdtman, "JSON Canonicalization Scheme (JCS)", RFC 8785, June 2020.
- [RFC 9180] Barnes, R., Bhargavan, K., Lipp, B., and C. Wood, "Hybrid Public Key Encryption", RFC 9180, February 2022.
[SCHEMA-BACKUP]
"wallet-backup-v1 JSON Schema", frozen version 1.
[SCHEMA-CUSTODIAN]
"wallet-custodian-key-v1 JSON Schema", frozen version 1.
[SCHEMA-SHARE]
"wallet-recovery-share-v1 JSON Schema", frozen version 1.
- [VECTORS] "wallet-backup-v1 Deterministic Conformance Vectors", frozen version 1.
15.2. Informative References
- [ARGON2] Biryukov, A., Dinu, D., Khovratovich, D., and S. Josefsson, "Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications", RFC 9106, September 2021.
Appendix A. Mandatory Algorithms Object
The algorithms object MUST equal the following JSON value:
{ "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" }
Appendix B. Normative Artifacts
The frozen schema identifiers remain under gandalfwallet.com. A documentation-domain change does not change these identifiers.
Backup envelope schema identifier: https://gandalfwallet.com/protocol/wallet-backup-v1.schema.json
Custodian key schema identifier: https://gandalfwallet.com/protocol/wallet-custodian-key-v1.schema.json
Recovery-share schema identifier: https://gandalfwallet.com/protocol/wallet-recovery-share-v1.schema.json
Repository source, schemas, vectors, and reference implementation: https://github.com/lmrrcc/gandalf/tree/main/gandalf-wallet
Authors' Address
Gandalf Contributors https://github.com/lmrrcc/gandalf