Security model
The short version: only the user's wallet can authorize their account. Not your app, not our backend. And the user can keep using their account even if we disappear.
#The account is a zero-trust dWallet
Each account is an Ika dWallet using 2PC-MPC: signing needs both the user's share of the key and the Ika network's share. Neither side can sign alone, and the full private key never exists anywhere.
The user's share never leaves their browser in the clear. It is encrypted to a key only the user holds and stored on Ika, so it can be recovered on any device from the wallet alone.
#The key comes from the user's wallet
When a user first signs in, their wallet signs a fixed message naming the Ika Accounts domain. The account's keys are derived from that signature:
- the key that decrypts their share of the dWallet,
- the key that signs their login to our API.
Only the wallet can produce that signature, so only the wallet can recreate the keys. Nothing is stored on our servers that could rebuild them.
This happens inside the key origin, ika-accounts.mpckit.xyz, in a hidden iframe and a small window, never in your page. On a device the user has used before, the derived secret stays sealed in that origin's storage under a non-extractable browser key. forgetDevice() erases it.
#Every signature is approved on-chain
Our backend submits requests to Ika through the Registry, a Move contract on Sui that holds every account's dWallet capability. The Registry only asks Ika to sign when the request carries one of:
- an approval signed by the owner wallet, naming the exact message to sign and this Registry, or
- a signature from a session key the owner granted, within its expiry and transaction count.
The Registry verifies EVM signatures (secp256k1) and Solana signatures (Ed25519) on-chain. A request without a valid approval is rejected by the contract, whoever sends it.
When an account is created, the Registry also checks that the new dWallet is bound to its owner wallet, so a dWallet can't be attached to someone else.
#Users can leave without us
If our service stops, or refuses a user, they don't lose their account:
- The encrypted share lives on Ika. The user decrypts it with their wallet alone.
registry::sign_permissionlesslets anyone sign for an account with the owner's approval, paying their own Ika fees and gas. No operator is involved.
The SDK ships this path (unlockFromChain, signWithoutOperator, executeWithoutOperator).
#What we can and can't do
| Sign a transaction for a user | No. The Registry requires the owner's approval. |
| Read or rebuild a user's key | No. It is derived from the wallet's signature, in the user's browser. |
| Refuse to sponsor a user's transactions | Yes. The user can then sign through sign_permissionless with their own gas. |
| Change the contracts | Only through a 7-day public timelock on the upgrade capability, or never, once it is made immutable. |
#What your app can and can't do
Your app decides which transactions to request and can grant itself a session scope. It can't approve anything on the user's behalf, can't see the user's keys, and can't exceed the session scope the user saw and accepted.
#Your responsibilities
- Add only your real origins under Allowed origins. The key frame refuses to run anywhere else.
- If your site sets a Content Security Policy, allow
https://ika-accounts.mpckit.xyzinframe-src, and don't block its pop-up. - Keep session scopes tight: your own packages and realistic spend caps.
#Verify it yourself
- The key origin publishes a hash of every file at
/manifest.json. Buildapps/keysand compare. - Mainnet contract addresses are in the reference.