Skip to main content
Unlink splits work between a Client surface (the user’s process, which holds the spending key) and an Engine (the Unlink backend, which builds Groth16 proofs and broadcasts on-chain). This page describes which secrets live where so you can pick the right integration shape — the SDK enforces these boundaries; you can verify the claim by reading the code in protocol/sdk.

Data flow

The two key nodes are grouped by trust profile: the spending key never leaves the client, while the viewing and nullifying keys both cross to the Engine exactly once, during registration.

Where each secret lives

What the Engine does and doesn’t see

  • Sees: the public Unlink address, the signed SigningRequest, viewing/nullifying keys after registration (so it can decrypt memos and reason about which notes are spendable).
  • Does not see: your spending key. Every authorisation is signed in the client process after the SDK checks the prepared operation and output artifacts against the requested transfer, withdrawal, or execution funding withdrawal. Before an EVM wallet signs a deposit or execution deposit-back, the SDK also derives the intended recipient note and reconstructs the prepared notes_hash. This prevents the Engine from substituting a different note owner, token, or amount while preserving the client-signed hash.
  • Generates: the Groth16 proof. Proof generation is heavy and runs on the Engine for performance; the witness is built from public state plus the client-supplied signature, so the proof is sound without the spending key.
  • Broadcasts via the relayer: gas sponsorship is paid by the relayer using the tenant’s relay balance.

Remaining Engine trust boundaries

Deposit verification is deterministic and stateless, but it does not make the Engine fully untrusted:
  • The SDK binds the returned ciphertext into notes_hash, but does not yet decrypt it and verify that its plaintext is the requested nonce, token, and amount. A compromised Engine cannot redirect note ownership after the NPK check, but malformed ciphertext can make the note undiscoverable.
  • The SDK currently obtains chain ID, pool address, and Permit2 address from the Engine’s environment endpoint. A named hosted environment pins the Engine URL, not those signature- and approval-critical values. Treat deployment configuration as trusted until the SDK independently pins and checks it.

Choosing the right integration shape

  • Browser (non-custodial) — use createUnlinkClient from @unlink-xyz/sdk/browser. The admin API key stays on your backend; the spending key stays in the browser, which only ever holds short-lived authorization tokens.
  • Server / custodial — use createUnlinkClient from @unlink-xyz/sdk/client with a server-held spending key. The admin API key and the spending key live in the same trusted process. You can still route signing to a remote key holder via a signSigningRequest callback so your backend never holds the spending key.
See Custody models for the full comparison.
This page is the integrator-facing trust model. The team-internal spec for auditors (full property list, adversaries, threat model) lives at docs/internal/spec/trust-model.md and is not published.