Skip to Content

Passphrase

Rules and best practices for handling passphrases (hidden wallets).

Core concepts

  • Standard wallet = seed + empty passphrase; hidden wallet = seed + non-empty passphrase (case-sensitive).
  • Every different passphrase generates a completely different wallet — even a single character change.
  • Passphrases cannot be recovered; back them up securely.
  • Force the standard wallet by setting useEmptyPassphrase: true.

Device support

Do not infer host or on-device input support from a model name alone. Read getDeviceState to identify the device, firmware, and current state, then follow the actual UI request. Availability depends on the device, firmware, SDK, and runtime.

Request or conditionHost behavior
REQUEST_PASSPHRASEPresent only the input choices supported by the request and target device. Preserve response correlation.
A request marked deviceOnlyAsk the user to continue on the device; do not collect the passphrase on the host.
REQUEST_PASSPHRASE_ON_DEVICENotification only; show device guidance and do not send uiResponse.
Requested input mode unavailableExplain the limitation; do not silently switch input methods.

Persist the wallet binding (deviceId and passphraseState) where appropriate, not a firmware session_id.

Interaction flow

Hardware SDK 1.2.x: searchDevices → getDeviceState → openWalletSession → address / sign with deviceId + passphraseState (or useEmptyPassphrase: true). Subscribe to UI_EVENT before opening a wallet. See Wallet Sessions.

Standard wallet

const state = await HardwareSDK.getDeviceState(connectId); if (!state.success) throw new Error(state.payload.error); const deviceId = state.payload.identity.deviceId; if (!deviceId) throw new Error("Device has no wallet identity"); const standard = await HardwareSDK.openWalletSession(connectId, { mode: 'standard' }); if (!standard.success) throw new Error(standard.payload.error); await HardwareSDK.evmGetAddress(connectId, deviceId, { path: "m/44'/60'/0'/0/0", useEmptyPassphrase: true, });

Hidden wallet (open once, reuse fingerprint)

import { UI_EVENT, UI_REQUEST, UI_RESPONSE } from '@onekeyfe/hd-core'; HardwareSDK.on(UI_EVENT, async (message) => { if (message.type === UI_REQUEST.REQUEST_PASSPHRASE) { await HardwareSDK.uiResponse({ type: UI_RESPONSE.RECEIVE_PASSPHRASE, ...message.payload.responseCorrelation, payload: { passphraseOnDevice: true, value: '', save: false }, }); } }); const opened = await HardwareSDK.openWalletSession(connectId, { mode: 'select-hidden', }); if (!opened.success) throw new Error(opened.payload.error); if (opened.payload.walletType !== 'hidden') { throw new Error('A hidden wallet was not opened'); } const deviceId = opened.payload.deviceId; const passphraseState = opened.payload.passphraseState; await HardwareSDK.evmGetAddress(connectId, deviceId, { path: "m/44'/60'/0'/0/0", passphraseState, }); await HardwareSDK.btcGetAddress(connectId, deviceId, { path: "m/84'/0'/0'/0/0", coin: 'btc', passphraseState, });

On-device vs software passphrase input:

// On-device payload: { passphraseOnDevice: true, value: '' } // Software payload: { value: userInput, passphraseOnDevice: false, save: false }

Later process / reconnect

Persist only deviceId + passphraseState. Do not persist firmware session_id. Load the binding and pass it on the next address / sign call. Core resumes inside that method. openWalletSession has no resume mode.

import { OpenWalletSessionMode } from '@onekeyfe/hd-core'; // Process 1 — after the user selects the hidden wallet const opened = await HardwareSDK.openWalletSession(connectId, { mode: OpenWalletSessionMode.SelectHidden, }); if (!opened.success) throw new Error(opened.payload.error); if (opened.payload.walletType !== 'hidden') { throw new Error('A hidden wallet was not opened'); } saveBinding({ deviceId: opened.payload.deviceId, passphraseState: opened.payload.passphraseState, }); // Process 2 — later process, same machine const { deviceId, passphraseState } = loadBinding(); await HardwareSDK.evmGetAddress(connectId, deviceId, { path: "m/44'/60'/0'/0/0", passphraseState, });

preloadSessionCache is a compatibility entry for old App/CLI Keychain records. New integrations should not call it and should not store session_id.

If the device locked between processes, PIN runs again on the first protected call. See Wallet Sessions.

Unlock-first pattern (CLI)

const state = await HardwareSDK.getDeviceState(connectId); if (!state.success) throw new Error(state.payload.error); if (!state.payload.status.unlocked) { await HardwareSDK.deviceUnlock(connectId, {}); } await HardwareSDK.evmGetAddress(connectId, savedDeviceId, { path: "m/44'/60'/0'/0/0", passphraseState: savedPassphraseState, });

Security tips

  • Never log or persist passphrases in plain text.
  • Use useEmptyPassphrase: true to prevent accidental hidden wallet creation.
  • Pro/Touch devices: PIN must be entered on-device; passphrase can be entered on-device or via software.

FAQ

What happens if I forget my passphrase?

Funds in the hidden wallet become permanently inaccessible. There is no way to recover a forgotten passphrase. Always keep a secure backup.

Is the passphrase the same as the PIN?

No. The PIN protects physical access to the device. The passphrase creates an entirely different wallet from the same seed. They serve different security purposes:

PINPassphrase
PurposeDevice access protectionHidden wallet creation
RecoveryCan be reset via device wipe + seed recoveryCannot be recovered
StorageStored on deviceDo not persist plaintext; SDK caching depends on the input flow and save
Case-sensitiveN/A (numeric)Yes

Can I have multiple hidden wallets?

Yes. Each unique passphrase creates a distinct hidden wallet. You can have as many hidden wallets as you want — one per unique passphrase.

Does the device validate my passphrase?

A newly entered passphrase can derive a different wallet. When a later call supplies an expected passphraseState, the SDK checks that binding and fails on a mismatch; do not treat a mistyped passphrase as the intended wallet.

What’s the difference between passphraseOnDevice and software input?

  • On-device: The passphrase is entered directly on the hardware device’s screen/keyboard. It never leaves the device.
  • Software input: The passphrase is typed in your app and sent to the device via SDK.

See also: Common Params, Events & UI Wiring.

On Protocol V1, select-hidden can return a standard wallet when passphrase protection is disabled. Check walletType before saving or using passphraseState.

Last updated on