Self-custody identity where the backend cannot sign for a user.
Identity on the XRP Ledger, with an iOS app in the App Store and a backend that has no access to user keys.
Secure Enclave
- Device auth key, P-256
- Challenge and response
Keychain
- XRPL master key
- Regular key, rotatable
V5 envelope
- AES-256-GCM
- Device, iCloud and recovery-code wraps
WYSIWYS
- Renders the exact intent
- Signs locally
Register
- Public metadata only
- Never a private key
Prepare
- Builds the unsigned transaction
- Returns the intent hash
Verify
- Signed blob against the intent
- A mismatch is rejected
Broadcast
- Submits to the ledger
- Returns the transaction hash
NFTokenMint
- XLS-20 identity anchor
- URI sciphr:v5:cid#sha256
SetRegularKey
- Installs the regular signing key
- Master stays cold
DIDSet
- References the public DID document
- Publicly resolvable
SignerListSet
- Records signer-list authorization
- Operator-assisted restore not deployed
No plaintext wallet key crosses the line between the device and the backend.
- The backend builds an unsigned transaction and returns it with the hash of what it intends.
- The device renders that exact intent for the user, who approves it, and signs on the device.
- The backend checks the signed blob against the intent it prepared. If they disagree, nothing is broadcast.
The backend was never allowed to hold a key.
Private keys and recovery secrets are generated on the device. Plaintext wallet secrets and day-to-day signing keys stay device-local; an encrypted master-key envelope can be stored off-device for recovery. SciPHR services cannot open that envelope or sign user-wallet transactions.
Most of the engineering in the product follows from that rule.
An encrypted key envelope, a rotating signing key, and verification before broadcast.
Identity is an XLS-20 NFT whose URI anchors an encrypted envelope by content hash. The master key is wrapped inside that envelope. Day-to-day signing uses a separate regular key that can be rotated and thrown away, so the master almost never has to come out.
The backend prepares an unsigned transaction, the device shows the user exactly what they are about to authorize and signs it locally, and the backend verifies the signed blob against the intent it prepared before broadcasting. If those two disagree, nothing goes to the ledger.
Recovery without a server-held key.
Recovery is where a self-custody design is easiest to compromise. An earlier version of ours had a service holding a co-signing seed. Holding that seed would have been custody, so it was removed.
What shipped instead: the user restores with a written recovery code. The ledger identifies the encrypted envelope in content-addressed storage; the device retrieves and verifies it, unwraps the master key locally and signs a transaction to install a fresh regular key. It was proven end to end on live testnet.
Approved by App Review. Live on the App Store.
Recovery was proven end to end on live testnet before any of it shipped, including the master-signed key rotation a real recovery depends on.
The app is in the App Store. The recovery path is the first thing I would put through an external audit, because it was redesigned late.