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
createUnlinkClientfrom@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
createUnlinkClientfrom@unlink-xyz/sdk/clientwith 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 asignSigningRequestcallback so your backend never holds the spending key.
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.