Wallet FIDO2 Unlock

Salvium wallet decryption with hardware authenticators

Status: Draft Version: 1.3-draft Updated: 2026-05-10

This document describes an optional unlock mechanism for Salvium wallet files in which the wallet master key, the symmetric key used to decrypt the spend key, is wrapped under a wrapping key derived from an external FIDO2 hardware authenticator using the hmac-secret extension. The mechanism substitutes for, or composes with, the existing PIN or passphrase based unlock. It works with any compliant external FIDO2 authenticator including YubiKey, SoloKey, Nitrokey, Feitian, and Token2.

The goal is to give wallet users a meaningful upgrade in at rest protection using hardware they already own, without requiring custom firmware, vendor specific integrations, or a third party security audit pipeline.

This specification deliberately excludes biometric authentication, including platform authenticators (iOS Face ID, Touch ID, and Android BiometricPrompt) and biometric verification on hardware authenticators. See section 10.4 for the rationale. Because platform authenticators are excluded, the user presence and user verification distinctions that operating system APIs blur on iOS and Android do not apply to this specification. The mechanism operates only against external CTAP2 authenticators, where these signals are well defined.

Contents
  1. Quick usage
  2. Scope and goals
  3. Threat model
  4. Design overview
  5. Wallet file format
  6. Enrollment ceremony
  7. Unlock ceremony
  8. Multiple authenticators and backups
  9. Recovery
  10. Unlock policies
  11. Security and privacy
  12. Backwards compatibility
  13. Examples
  14. References
1. Quick usage

A wallet that supports this specification can offer the user a choice at wallet creation time, or as a setting on an existing wallet, between a PIN unlock, a hardware authenticator unlock, or both. The user touches a FIDO2 device once at enrollment to register it, and once at every unlock to authorise decryption.

$ whiskywallet open my-wallet Touch your authenticator to unlock... [touch detected, wallet unlocked]

The spend key is decrypted in memory only after the touch. The wallet behaves identically thereafter to a PIN unlocked wallet, with the same scanning, signing, and broadcasting paths.

2. Scope and goals

This specification defines:

  • The wallet file additions required to record one or more FIDO2 unlock entries alongside, or in place of, a PIN entry.
  • The cryptographic construction by which an authenticator's hmac-secret output is turned into a wrapping key that decrypts the wallet master key.
  • The enrollment and unlock ceremonies a wallet must perform.
  • The policy options a wallet should expose to the user, including PIN composition, multi authenticator backup, and recovery via seed.
  • The security properties the mechanism does and does not provide.

This specification does not define:

  • The on chain transaction format or any cryptographic operation specific to Salvium.
  • The wallet's user interface beyond the minimum disclosure required during enrollment and unlock.
  • Any change to the protections in place once the wallet is unlocked. The mechanism gates decryption only.
3. Threat model

Threats addressed

This mechanism is designed to defend against the following:

  • Wallet file theft. An attacker who copies the wallet file off the user's disk cannot decrypt it without physical possession of an enrolled authenticator. This is the dominant threat for users who back up their wallet to cloud storage, sync it across devices, or carry it on a laptop.
  • PIN capture. A keylogger, screen recorder, or shoulder surfer who captures the user's PIN gains nothing if the wallet is configured to require an authenticator. Composition of PIN and authenticator (see section 10) defends against the case where one factor is compromised.
  • Casual physical access. A laptop left unlocked and unattended cannot be used to spend if the authenticator is not also present at that moment.
  • Persistent malware before unlock. Malware sitting on the host cannot decrypt the spend key without a user touch on an enrolled authenticator. The authenticator's user presence requirement forces a physical action that malware cannot fake. The case where malware achieves code execution while the wallet is already unlocked is covered separately under threats not addressed below.

Threats not addressed

The mechanism does not defend against the following, and users with these threat models should consider alternatives:

  • Memory extraction during signing. Once the wallet is unlocked, the spend key resides in process memory. An attacker with code execution on the host while the wallet is unlocked can extract the key. A true hardware wallet, where the key never leaves the device, is the appropriate defence. This mechanism does not provide that property.
  • Malicious wallet binary. An attacker who can replace the wallet binary on disk can ship a version that bypasses the authenticator check. Code signing and reproducible builds mitigate this. The mechanism itself does not.
  • Authenticator and wallet file together. An attacker who has both the wallet file and physical possession of the authenticator has the same access as the legitimate user. PIN composition addresses this case at the cost of additional friction.
  • Defective or malicious authenticators. The mechanism trusts the authenticator's enforcement of user presence. An authenticator that returns an hmac-secret output without a touch breaks the model. Users should source authenticators from reputable vendors and verify firmware where possible.

Honest framing

This is good security for normal users at low cost. It is not maximum security for hostile environments. Users with adversaries capable of running code on their host while they are signing should use a true hardware wallet when one is available, and treat this mechanism as an interim or supplementary defence.

The specification also assumes users who can manage seed phrase backup discipline. Loss of all enrolled authenticators without a recovery PIN entry, or loss of the wallet file together with all authenticators, forces recovery from seed. Users who cannot reliably store and protect a seed offline may be better served by custodial wallets or by designs that include guided recovery, because the failure mode of this specification is permanent inaccessibility rather than degraded access.

4. Design overview

The wallet master key (WMK) is a single 32 byte key, generated once at wallet creation as fresh random bytes and never changed for the life of the wallet. The WMK encrypts the spend key and any other sensitive material at rest using XChaCha20-Poly1305 with a fresh nonce per encryption. What changes between unlock methods is not the WMK itself, but the wrapping key used to encrypt the WMK in each unlock entry.

Each unlock entry stores the WMK encrypted under a wrapping key that the entry's inputs reproduce at unlock time. The wrapping key is derived as follows for each method.

PIN unlock

wrapping_key = Argon2id(passphrase, salt_pin, params)

FIDO2 unlock

hmac_output = authenticator.hmac_secret(credential_id, salt_fido2) wrapping_key = HKDF-SHA256(hmac_output, info = "wwallet-fido2-v1")

PIN and FIDO2 composed

k_pin = Argon2id(passphrase, salt_pin, params) hmac_output = authenticator.hmac_secret(credential_id, salt_fido2) wrapping_key = HKDF-SHA256(k_pin || hmac_output, info = "wwallet-pin-fido2-v1")

The wallet file stores the spend key encrypted under the WMK, the WMK encrypted under one or more wrapping keys (one per unlock entry), and the parameters needed to reproduce each wrapping key. An installation can therefore carry, for example, a primary YubiKey entry, a backup YubiKey entry, and a recovery PIN entry, and any one of them is sufficient to unlock by reproducing its wrapping key and decrypting the wrapped WMK. Wrapping keys themselves are ephemeral and exist only in memory during unlock.

5. Wallet file format

This section specifies the additions to the wallet file. Existing fields not relevant to unlock are unchanged.

Header

The wallet file gains an unlock section containing one or more unlock entries. Each entry is independently capable of unwrapping the wallet master key from the parameters it stores.

unlock: version: 1 default_entry: "primary-yubikey" entries: - id: "primary-yubikey" method: "fido2" rp_id: "wallet.salvium.invalid" credential_id: <bytes, base64> salt: <32 bytes, base64> kdf: "hkdf-sha256" info: "wwallet-fido2-v1" wmk_wrapped: <bytes, base64> wmk_nonce: <bytes, base64> - id: "backup-yubikey" method: "fido2" rp_id: "wallet.salvium.invalid" credential_id: <bytes, base64> salt: <32 bytes, base64> kdf: "hkdf-sha256" info: "wwallet-fido2-v1" wmk_wrapped: <bytes, base64> wmk_nonce: <bytes, base64> - id: "recovery-pin" method: "pin" kdf: "argon2id" argon2_salt: <16 bytes, base64> argon2_params: memory_kib: 262144 iterations: 3 parallelism: 1 wmk_wrapped: <bytes, base64> wmk_nonce: <bytes, base64>

Field semantics

FieldDescription
versionSchema version, always 1 for this revision.
default_entryThe id of the entry the wallet should attempt first at unlock. The user can override at unlock time.
idA user readable label for the entry. The wallet uses it in prompts.
methodOne of fido2, pin, or pin+fido2.
rp_idThe FIDO2 relying party identifier the credential was created under.
credential_idThe opaque credential identifier returned by the authenticator at enrollment.
saltThe 32 byte salt passed to the authenticator's hmac-secret evaluation.
kdfThe key derivation function. hkdf-sha256 is required for all fido2 and pin+fido2 entries.
infoThe HKDF info string. Distinct values for fido2 and pin+fido2 provide domain separation.
argon2_saltThe 16 byte salt for the Argon2id derivation. Present on pin and pin+fido2 entries.
argon2_paramsThe Argon2id cost parameters: memory_kib (memory cost in kibibytes), iterations (time cost), and parallelism (lanes). Present on pin and pin+fido2 entries.
wmk_wrappedThe wallet master key, encrypted under the wrapping key reproduced from the entry's unlock material.
wmk_nonceThe nonce used for wmk_wrapped.

Field combinations per method

Not every field applies to every method. The required fields per method are as follows.

  • fido2 entries require rp_id, credential_id, salt, kdf, info, wmk_wrapped, and wmk_nonce. The kdf must be hkdf-sha256.
  • pin entries require kdf, argon2_salt, argon2_params, wmk_wrapped, and wmk_nonce. The kdf must be argon2id.
  • pin+fido2 entries require the FIDO2 fields (rp_id, credential_id, salt, and info) and the PIN fields (argon2_salt and argon2_params) together with kdf, wmk_wrapped, and wmk_nonce. The kdf must be hkdf-sha256. The PIN derivation runs Argon2id over the passphrase with the entry's Argon2 parameters, the FIDO2 derivation runs hmac-secret on the authenticator with credential_id and salt, the two outputs are concatenated in the order PIN first and FIDO2 second, and HKDF-SHA256 with the entry's info string produces the wrapping key.

Wallet identifier

The wallet file carries a stable identifier in its main header, distinct from the unlock section. The identifier is a 16 byte random value generated once at wallet creation, encoded as a hexadecimal string in the wallet file. It does not contain user identifying information and is not derived from the spend key or the public address. Its purpose is to bind authenticated material, including the associated data of every wrapped unlock entry and the FIDO2 user.id handle used at credential creation, to one specific wallet instance. An unlock entry copied from one wallet to another therefore cannot be silently substituted, because authenticated decryption fails when the destination wallet's identifier does not match the identifier bound into the entry's associated data.

Encryption parameters

The wrap is XChaCha20-Poly1305 with a 192 bit nonce. The associated data is the entry id concatenated with the wallet's stable identifier, both as UTF-8 bytes, separated by a single 0x00 byte. This binds the wrap to its entry and prevents an attacker from substituting a wrap from a different entry.

6. Enrollment ceremony

Enrollment is the act of binding an authenticator to the wallet by creating a credential and storing the unlock entry that references it.

Inputs

  • The wallet master key, currently in memory because the wallet is open.
  • A connected, user controlled FIDO2 authenticator.
  • A user supplied label for the entry, for example, primary-yubikey.

Steps

  1. Generate a random 32 byte salt.
  2. Request that the authenticator create a new credential with the hmac-secret extension enabled, using rp_id = "wallet.salvium.invalid" and user.id set to the 16 byte binary form of the wallet's stable identifier. The user verifies the touch.
  3. Receive the new credential_id from the authenticator.
  4. Immediately, in the same session, request an hmac-secret evaluation against the new credential using the salt from step 1. The user verifies the touch a second time. This confirms the credential is usable for unlock and produces hmac_output.
  5. Derive a wrapping key as HKDF-SHA256(hmac_output, info = "wwallet-fido2-v1").
  6. Encrypt the wallet master key under the wrapping key with a fresh nonce, producing wmk_wrapped and wmk_nonce.
  7. Append a new entry to the wallet file's unlock.entries list with the values above.
  8. Persist the updated wallet file atomically.

Disclosures

Before step 2, the wallet must display to the user that a new credential will be created on the authenticator, that the credential will be used to derive a key that decrypts the wallet, that loss of the authenticator without a backup will require recovery from the wallet seed, and the relying party id under which the credential will be registered. The user must explicitly confirm before the wallet proceeds.

7. Unlock ceremony

Unlock is the act of decrypting the wallet master key from a stored unlock entry.

Inputs

  • The wallet file, including the unlock section.
  • A connected, user controlled FIDO2 authenticator that holds an enrolled credential.

Steps

  1. Read the unlock.entries list and select the default entry, or prompt the user to select an entry.
  2. For an entry with method = "fido2": request an hmac-secret evaluation from the authenticator using the entry's credential_id and salt. The user verifies the touch. Derive the wrapping key as HKDF-SHA256(hmac_output, info = entry.info). Decrypt wmk_wrapped under the wrapping key. On authentication failure, present an error and offer to try a different entry.
  3. For an entry with method = "pin": prompt the user for the passphrase. Derive the wrapping key as Argon2id(passphrase, argon2_salt, argon2_params). Decrypt wmk_wrapped under the wrapping key. On authentication failure, increment a local rate limit counter and prompt again.
  4. For an entry with method = "pin+fido2", perform both derivations as in steps 2 and 3, concatenate the outputs in the order PIN first and FIDO2 second, derive the wrapping key as HKDF-SHA256 over the concatenation with the entry's info string, and decrypt wmk_wrapped under the wrapping key.
  5. With the wallet master key in hand, decrypt the spend key and proceed.

Authenticator absent

If no enrolled authenticator is present, the wallet should present the list of enrolled entries with their labels, allow the user to select a different entry, and if only FIDO2 entries are enrolled and no authenticator is present, instruct the user to connect one or to use the recovery flow described in section 9. The wallet must not expose the existence of credential ids, salts, or other metadata to the user beyond the entry labels they chose at enrollment.

8. Multiple authenticators and backups

A user should enroll at least two authenticators. Loss or destruction of the only enrolled authenticator forces a recovery from seed, which is recoverable but disruptive.

Recommended setup

  • Primary authenticator, kept on the user's keyring or person.
  • Backup authenticator, kept in a physically separate, secure location such as a safe.
  • Optional recovery PIN, with a strong passphrase, for use when neither authenticator is reachable.

Each authenticator gets its own unlock entry with its own credential_id and salt. Compromise of one entry does not affect the others, because each entry independently wraps the wallet master key under its own wrapping key.

Adding an authenticator later

Adding a new authenticator requires the wallet to be currently unlocked, because the wallet master key must be in memory to be wrapped under the new credential. The user opens the wallet by any existing entry, then runs the enrollment ceremony from section 6 for the new authenticator.

Removing an authenticator

The user can remove an entry from the unlock.entries list at any time. The wallet should require a confirmation that names the entry being removed, and should refuse to remove the last remaining entry without an explicit warning that doing so will leave the wallet inaccessible.

9. Recovery

The wallet seed remains the master recovery mechanism. If all enrolled authenticators are lost and no recovery PIN entry exists, or if the wallet file itself is lost, the user restores from seed.

A restored wallet is a fresh installation. The new installation has no unlock entries until the user enrolls them. The seed itself is not, and must not be, stored alongside the wallet file. It is held by the user offline.

This is unchanged from existing wallet practice. The FIDO2 unlock mechanism does not weaken the seed based recovery story; it only changes the path used during normal day to day unlock.

10. Unlock policies

A wallet should expose three policy options to the user at wallet creation, and should allow them to be changed on an existing wallet provided the wallet is currently unlocked. A fourth subsection below documents methods that are deliberately excluded from this specification.

10.1 PIN only

Equivalent to today's behaviour. A single pin entry. Recommended only for wallets that hold small amounts or for users who cannot use FIDO2 for accessibility or environmental reasons.

10.2 FIDO2 only

One or more fido2 entries, no pin entry. Loss of all authenticators forces recovery from seed. Recommended for users who maintain at least two authenticators in physically separated locations.

10.3 PIN and FIDO2 composed

One or more pin+fido2 entries. The user must present both a passphrase and an authenticator touch to unlock. This raises the bar to require both factor types simultaneously and defends against the case where one factor is compromised.

A wallet may also offer a hybrid configuration in which a pin+fido2 entry is the default and a separate pin entry exists as a recovery path with a stronger passphrase. The user trades the convenience of a remembered short passphrase for the convenience of being able to unlock without the authenticator in extremis.

10.4 Excluded methods

This specification deliberately does not support biometric authentication, neither through platform authenticators (iOS Face ID, Touch ID, and Android BiometricPrompt) nor through biometric verification on hardware authenticators, for example fingerprint security keys such as YubiKey Bio or Feitian K9.

Biometric authentication is excluded for the following reasons.

  • Biometric templates cannot be rotated. A fingerprint, face, or iris scan that is compromised once is compromised for the lifetime of the user. Unlike a PIN that can be changed or a hardware key that can be replaced, a leaked biometric template represents a permanent compromise of that authentication factor.
  • Biometric authentication has weaker legal protection against compulsion in many jurisdictions than knowledge based authentication. Courts have repeatedly held that compelling biometric unlock is not equivalent to compelling disclosure of a passphrase, leaving users with materially different protections depending on which factor they chose.
  • Biometric template storage and processing varies significantly across platforms, devices, and manufacturers. The security guarantees offered by a flagship phone's secure enclave are not equivalent to those offered by a budget device's biometric subsystem, and the user typically cannot verify which they are relying on.

Wallet implementations of this specification must not enroll biometric credentials. The wallet must not call platform biometric APIs (ASAuthorization for biometric on iOS, BiometricPrompt on Android, and equivalent APIs on other platforms) for the purpose of unlocking the wallet.

Hardware authenticators that include biometric verification capabilities (fingerprint sensors) may be used only in modes that rely on the authenticator's own user presence verification through touch, not through biometric verification. Wallet implementations should configure FIDO2 requests in a way that does not solicit biometric user verification from the authenticator, and should warn users at enrollment time if they attempt to enroll a biometric equipped authenticator.

Users who require biometric unlock convenience are encouraged to use a different wallet that meets their threat model. This specification is not a complete solution for all users; it is a complete solution for users who share its assumptions.

11. Security and privacy

Salt uniqueness

Each unlock entry must use an independently generated 32 byte random salt. Reusing the salt across entries does not directly leak the wallet master key, because the entries are wrapped independently, but it weakens the domain separation between credentials and is unnecessary given that salts are cheap.

KDF parameters

Argon2id parameters for pin and pin+fido2 entries should be selected to make a single derivation take roughly 250 to 500 milliseconds on the user's hardware. The defaults of memory_kib = 262144, iterations = 3, parallelism = 1 are reasonable for laptops and desktops in 2026 and should be revisited as hardware evolves.

HKDF-SHA256 is used in extract and expand mode. The info string provides domain separation between methods: wwallet-fido2-v1 for FIDO2 only, wwallet-pin-fido2-v1 for the composed method.

Offline brute force on PIN entries

An attacker who obtains a copy of the wallet file containing a pin entry can attempt an offline Argon2id brute force against the passphrase, at their own pace and on their own hardware. The strength of the passphrase is therefore the floor on the at rest security of any wallet that includes a pure pin entry. A short or low entropy passphrase is recoverable by an attacker with modest GPU resources, regardless of the Argon2id parameters chosen, because the parameters only slow the attacker linearly while passphrase weakness reduces the search space exponentially. Wallets that include a pin entry should require, or at minimum strongly recommend, a passphrase of meaningful length and entropy. Pure fido2 entries and pin+fido2 entries are not exposed to this attack, because the FIDO2 component requires physical possession of the authenticator and cannot be brute forced offline.

Authenticated encryption

The wrap of the wallet master key uses XChaCha20-Poly1305. The associated data binds the wrap to the entry id and the wallet identifier. Tampering with any of those fields invalidates the wrap. A wallet must reject a wrap whose authenticated decryption fails, and must not silently fall back to a different entry without explicit user direction.

Relying party id

The recommended rp_id is wallet.salvium.invalid. The .invalid top level domain is reserved by RFC 2606 and is guaranteed never to resolve, on the local network or on the public internet. The wallet performs CTAP2 operations directly against the authenticator over USB or NFC, so no DNS resolution or origin validation is involved at any point in enrollment or unlock. The rp_id serves only to namespace credentials, so that on authenticators with credential management user interfaces, the user can identify the credential as belonging to a Salvium wallet. Wallet implementers should agree on a single rp_id to allow a single authenticator to be used across multiple Salvium wallet implementations without conflict.

Earlier drafts of this specification used wallet.salvium.local. The .local suffix is reserved for multicast DNS by RFC 6762 and triggers mDNS lookups on systems that have it enabled, which leaks the chosen string on the local network and is therefore incorrect for a value that should never be resolved at all. The .invalid suffix is the correct choice for this purpose.

This choice is correct only for wallets that talk to authenticators directly over CTAP2, for example a desktop binary linked against libfido2, a Tauri or Electron application using the same path, or a command line utility. A future variant of this specification that targeted browser mediated WebAuthn flows through navigator.credentials, mobile platform FIDO2 APIs, or any environment in which the browser or operating system validates the relying party identifier against the page or application origin, could not reuse a reserved suffix such as .invalid. The browser or platform would reject the request with a security error before it ever reached the authenticator, because the relying party identifier in those environments must be a registrable domain on the Public Suffix List, with appropriate Apple Associated Domains or Android Asset Links bindings for mobile applications. A wallet variant of that kind would require a real domain that the implementer controls, and that change should be treated as a substantive design decision rather than a configuration adjustment.

Touch policy

The hmac-secret extension requires user presence on every evaluation: the authenticator must observe a physical touch before producing the HMAC output. The wallet must request user presence and must not configure the request in a way that suppresses it. A wallet should reject an authenticator that returns an hmac-secret output without observable user presence, where the authenticator's policy makes that detectable.

User verification, defined in FIDO terminology as a stronger check such as a PIN entered on the authenticator or a biometric scan, is a property distinct from user presence. Where the authenticator supports both touch based user presence and biometric user verification, the wallet must request only the touch based variant. The wallet must not require user verification on the request (the WebAuthn userVerification = "required" option, or equivalently the CTAP2 uv option), because requiring it on a biometric equipped authenticator would invoke the fingerprint sensor or other biometric subsystem. Authenticators that cannot satisfy a touch only request must be rejected at enrollment time with a clear message to the user explaining that biometric authentication is not supported by this specification.

Authenticator compatibility requirements

A compliant authenticator for this specification must support CTAP 2.0 or later, must implement the hmac-secret extension, and must require a physical touch as the user presence signal on every credential operation. Authenticators that lack any of these properties are not usable and must be rejected at enrollment time with a clear message to the user.

External hardware authenticators known to be compatible with hmac-secret backed key derivation, based on community use with systemd-cryptenroll and age-plugin-fido2-hmac, include the YubiKey 5 series, the YubiKey Bio in touch only mode, the SoloKey 2, the Nitrokey 3, and Token2 series authenticators. Wallet implementers should verify behaviour against the specific firmware version in use, because vendor implementations of optional extensions vary, and should publish their own compatibility results so that users can select hardware with confidence.

Operating system integration is a separate concern from authenticator capability. Linux installations require a FIDO2 udev rule set so that a non root user can access the authenticator, Windows requires a current WebAuthn or libfido2 path for direct CTAP2 access, and macOS exposes external authenticators through its own framework. An authenticator that is technically compliant can still fail at the host integration layer if these prerequisites are not met, and wallet implementations should produce diagnostic output that distinguishes authenticator failure from host failure.

Credential discoverability

The credential should be created as a non discoverable (non resident) credential where possible. The credential_id is stored in the wallet file and presented to the authenticator at unlock, so discoverability is not required, and a non discoverable credential consumes no slot in the authenticator's resident credential storage.

Privacy of the wallet file

The wallet file, including the unlock section, should not be uploaded to third party services without consideration. The credential id and salt do not leak the spend key, but they do reveal that the file is a Salvium wallet using FIDO2 unlock, and they bind the file to specific authenticators a user holds.

Side channels

The wallet must avoid timing differences when handling authentication failures across entries. An attacker with access to the wallet file should not be able to learn which entry produced a successful decryption from observing the wallet's behaviour.

12. Backwards compatibility

The unlock section is strictly additive. A wallet that does not implement this specification will encounter an unlock section it does not understand. The recommended behaviour for a non implementing wallet is to detect the presence of the unlock section, refuse to open the wallet and surface a clear error indicating that the wallet uses a newer unlock format the current wallet does not support, and direct the user to a wallet that does support the format, or to recover from seed into the current wallet's native format.

Wallet files that have never been migrated to the new format remain readable by older wallets without change.

A future version of this specification may define a migration path that allows a wallet to express both the old format and the new format simultaneously, at the cost of carrying two encryption schemes in the same file. The current version does not require this.

13. Examples

Minimal FIDO2 only wallet

A user creates a new wallet with a single primary YubiKey and accepts the loss of the key as a recovery from seed event.

unlock: version: 1 default_entry: "primary" entries: - id: "primary" method: "fido2" rp_id: "wallet.salvium.invalid" credential_id: "AAEC..." salt: "kJh..." kdf: "hkdf-sha256" info: "wwallet-fido2-v1" wmk_wrapped: "..." wmk_nonce: "..."

Two authenticators with a recovery PIN

A user enrolls two authenticators and a strong recovery passphrase.

unlock: version: 1 default_entry: "primary" entries: - id: "primary" method: "fido2" ... - id: "backup" method: "fido2" ... - id: "recovery" method: "pin" kdf: "argon2id" argon2_salt: "..." argon2_params: memory_kib: 524288 iterations: 4 parallelism: 1 wmk_wrapped: "..." wmk_nonce: "..."

PIN and FIDO2 composed

A user wants both factors required at every unlock.

unlock: version: 1 default_entry: "primary" entries: - id: "primary" method: "pin+fido2" rp_id: "wallet.salvium.invalid" credential_id: "..." salt: "..." kdf: "hkdf-sha256" info: "wwallet-pin-fido2-v1" argon2_salt: "..." argon2_params: memory_kib: 262144 iterations: 3 parallelism: 1 wmk_wrapped: "..." wmk_nonce: "..."
14. References
  • FIDO Alliance, Client to Authenticator Protocol (CTAP) 2.1, including the hmac-secret extension.
  • W3C, Web Authentication: An API for accessing Public Key Credentials, Level 3.
  • RFC 5869, HMAC-based Extract-and-Expand Key Derivation Function (HKDF).
  • RFC 9106, Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications.
  • IETF Draft, XChaCha: eXtended-nonce ChaCha and AEAD_XChaCha20_Poly1305.
  • systemd-cryptenroll documentation, as a reference implementation of FIDO2 hmac-secret for symmetric key derivation in disk encryption.
  • age-plugin-fido2-hmac source, as a Rust reference implementation of FIDO2 backed key wrapping using hmac-secret.

Comments and proposed revisions are welcome. This specification is a working draft maintained on whiskymine.io and intended for adoption by Salvium wallet implementers who wish to offer hardware gated unlock as an option to their users.