tenzro-device-key
Hardware-backed device keys for Tenzro — macOS/iOS Secure Enclave P-256
signing + ECIES secret-wrapping, plus a biometric KeystoreUnlocker for
passkey-gated wallet persistence.
A device key is a non-extractable P-256 keypair living in the platform's
hardware-rooted secure element. On macOS/iOS that is the Secure Enclave (via
security-framework + kSecAttrTokenIDSecureEnclave), gated by Touch ID /
Face ID. The private key never enters process memory.
Two capabilities
- Signing (
DeviceKey::sign_prehash) — sign a 32-byte digest; the OS triggers the user-presence (Touch ID / Face ID) ceremony. - Secret wrapping (
DeviceKey::wrap_secret/unwrap_secret) — ECIES- encrypt an arbitrary secret to the device public key (no biometrics), and decrypt it with the private key (biometrics). The decrypt output is stable across calls, which makes this the right primitive for deriving a persistent keystore password.
SecureEnclaveUnlocker
SecureEnclaveUnlocker implements tenzro-keystore-unlock's
KeystoreUnlocker. On first run it creates an SE key, generates a random
keystore password, wraps it to the key, and persists the ciphertext. On later
runs it reopens the key and unwraps the ciphertext (Touch ID) to recover the
same password — so an embedded tenzro-node's FROST wallet persists across
restarts.
use Arc;
use SecureEnclaveUnlocker;
let unlocker = new;
let handle = spawn_in_background_with_unlocker.await?;
Persistence
Cross-restart unlock works on a plain Developer-ID build with no extra
entitlements: the Secure-Enclave key is persisted in the legacy file (login)
keychain via Location::DefaultFileKeychain, which sets kSecAttrIsPermanent
without requiring the keychain-access-groups entitlement or a provisioning
profile (those are needed only by the data-protection keychain — see Apple
TN3137). Touch ID / Face ID still gates every signing operation. A later run
reopens the key by label and unwraps the on-disk ciphertext to recover the same
password. If the key is gone (a different machine, or a reset login keychain),
the unlocker reports the wallet as unavailable and the node recreates it.
Non-Apple platforms currently compile to a stub backend.
License
Apache-2.0