@digitalshieldfe/airgap-keyring
v0.1.3
Published
Mnemonic generate/import and SoftSigner for Digital Shield cold SDK (JS demo; Native later)
Readme
@digitalshieldfe/airgap-keyring
- ColdVault: PIN-wrapped encrypted mnemonic in host sandbox storage
- SoftKeyring: JS SoftSigner for Node CLI / unit tests only
- ColdSigner types: no
getSeed/getSecpRoot— production uses@digitalshieldfe/airgap-secure-keyring
Production RN cold apps:
import { createNativeSecureKeyring } from "@digitalshieldfe/airgap-secure-keyring";
import { createColdVault } from "@digitalshieldfe/airgap-keyring";
const keyring = await createNativeSecureKeyring();
const vault = await createColdVault(storage, {
pinKdf,
keyring,
holdMnemonicInJs: false,
});Onboarding API
Host collects PIN + mnemonic in the UI, then initializes once:
// Create path — generate only (no vault pending state)
const { mnemonic } = await vault.generateMnemonic({ strength: 128 });
// … host backup / verify UI …
// Create or import — single commit
await vault.initializeWallet({
pin,
confirmPin: pin,
words: mnemonic, // or imported phrase
});initializeWallet runs PBKDF2 → encrypts the mnemonic into sandbox storage →
loads the seed into the keyring. There is no separate preparePin /
confirmBackupAndCommit / restoreWallet pending flow.
PIN / backup screens belong in the host app. Host must pass app-sandbox storage if uninstall should wipe the ciphertext.
AirGapError (re-exported)
Vault, PIN, and mnemonic errors use stable codes from @digitalshieldfe/airgap-core:
import {
PinLockedError,
VaultWipedError,
PIN_MAX_FAILED_ATTEMPTS,
AirGapError,
AirGapErrorCode,
isAirGapError,
getAirGapErrorCode,
} from "@digitalshieldfe/airgap-keyring";PIN unlock flow
try {
await vault.unlock(pin);
} catch (e) {
if (e instanceof VaultWipedError) {
// code === PIN_VAULT_WIPED
return;
}
if (e instanceof PinLockedError) {
const { failedAttempts } = e;
const max = e.params?.maxAttempts ?? PIN_MAX_FAILED_ATTEMPTS;
// Map PIN_WRONG_WITH_ATTEMPTS — do not parse e.message
showToast(t("pin_wrong", { current: failedAttempts, max }));
return;
}
if (isAirGapError(e)) {
showToast(mapCode(e.code, e.params) ?? e.message);
}
}| code | When |
|--------|------|
| PIN_WRONG_WITH_ATTEMPTS | Unlock with wrong PIN (params.failedAttempts, params.maxAttempts) |
| PIN_VAULT_WIPED | 10 failed unlocks; vault cleared |
| PIN_WRONG | Change PIN: old PIN incorrect (no counter) |
| PIN_MISMATCH | Two PIN entries do not match |
| VAULT_LOCKED | Sign/export before unlock |
| MNEMONIC_INVALID_BIP39 | Invalid backup words |
PinLockedError / VaultWipedError extend AirGapError and remain usable with instanceof.
See root README §12.
