Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions docs/concepts.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: "Core Concepts"
description: "The five things every Lit app is made of — accounts, API keys, wallets (PKPs), Lit Actions, and groups — and how they fit together."
---

Every Lit integration, whether it signs or encrypts, is assembled from the same five pieces. This page defines each one once, shows how they connect, and points to the administrative page where you manage it.

```
Account (your identity; holds the credit balance)
├── Account API key (master credential — admin only, never ship it)
├── Usage API keys (scoped, rotatable — what your app actually uses)
└── Group (the permission boundary)
├── PKP (wallet) — a key pair the network holds for you
└── Lit Action (CID) — the code allowed to use those PKPs
```

When you call the `/core/v1/lit_action` endpoint, the server checks — before running anything — that your API key is allowed to execute that action code in a group, and that the PKP the action wants to use is permitted in the same group. That single rule is the whole security model from the developer's side.

## Account

Your account is your identity on Lit. Creating one registers it on-chain (on Base) and takes about 15 seconds; everything you create afterwards — keys, wallets, groups — hangs off it, and its credit balance pays for metered operations.

Accounts come in two ownership flavors: **API mode** (the default — a server-generated API key is the credential) and **ChainSecured mode** (a wallet you control owns the account on-chain). Start in API mode; you can convert later, one-way.

<Card title="Admin: Account Modes" icon="users-gear" href="/management/account_modes" horizontal>
Ownership models, converting API → ChainSecured, and transferring ownership.
</Card>

## API keys: account key vs. usage keys

Lit has exactly two kinds of API key, and confusing them is the most common first-day mistake:

| | Account key | Usage key |
|---|---|---|
| Created | Once, at account creation — **shown once, never again** | On demand, as many as you like |
| Scope | Full admin access to the account | Only the groups it's been granted |
| Rotatable | No — it *is* your account identity | Yes — create and delete freely |
| Belongs in | A secrets manager, admin tooling | Your dApp, servers, cron jobs |

Rule of thumb: the account key *configures*, usage keys *execute*. Anything that ships to production or to a user should hold a usage key scoped to one group.

<Card title="Admin: API Keys" icon="key" href="/management/api_keys" horizontal>
Permission fields, key lifecycle, and managing keys via Dashboard or API.
</Card>

## PKPs (wallets)

A Programmable Key Pair is a real wallet — an elliptic-curve key pair — whose private key lives inside the Lit network's TEE and never leaves it. Your account can mint many. A PKP does two jobs:

- **Signing** — an authorized Lit Action retrieves the key with `Lit.Actions.getPrivateKey({ pkpId })` and signs messages or transactions with it (via ethers.js).
- **Encryption** — `Lit.Actions.Encrypt` / `Lit.Actions.Decrypt` use an AES key derived from a PKP, so "who can decrypt" reduces to "which actions may use this PKP."

Because a PKP is an ordinary EVM key pair, its address can hold funds, own NFTs, and be verified with `ecrecover` — the difference is that *code*, not a person with a seed phrase, decides when it signs.

<Card title="Admin: create PKPs" icon="wallet" href="/management/dashboard#4-request-new-pkps-wallets" horizontal>
Mint wallets from the Dashboard, or via `POST /core/v1/create_wallet`.
</Card>

## Lit Actions

A Lit Action is an immutable JavaScript program: you publish it to IPFS, and its content hash (CID) becomes both its address and a permanent commitment to its behavior — change one byte and you have a different action. Actions run inside the network's TEE with access to the [Lit Actions SDK](/lit-actions/chipotle): PKP signing, encryption/decryption, and plain `fetch` to any HTTP endpoint.

For quick iteration you can also pass raw code to the run endpoint without publishing to IPFS — permission checks then apply to the hash of that code.

<Card title="Lit Actions overview" icon="bolt" href="/lit-actions/index" horizontal>
The runtime, PKP access, proofs, and a minimal example.
</Card>

## Groups

A group is the permission boundary that ties the other pieces together. It contains the PKPs that may be used, the action CIDs that may run, and it's what usage keys are scoped to. On-chain, this lives in the `AccountConfig` contract — the network enforces it on every call.

Typical setup: one group per app (or per environment), holding that app's PKP(s) and permitted action(s), with one usage key granted `execute_in_groups` for it.

<Card title="Groups in depth" icon="folder-tree" href="/architecture/groups" horizontal>
How on-chain group configuration binds PKPs, CIDs, and keys.
</Card>

## Funding and metering

Running actions and *write* management calls (creating keys, PKPs, groups) consume credits from your account balance; all *read* calls are free. An unfunded metered call fails with `402 Payment Required`. You can fund with a credit card, [crypto](/management/crypto), or [LITKEY](/management/litkey).

<Card title="Admin: Pricing" icon="credit-card" href="/management/pricing" horizontal>
Credit packages, per-call and per-second rates, and funding options.
</Card>

## Where to next

- [Quick Start](/quickstart) — create an account and run your first action.
- [Signing guide](/guides/signing) — from a fresh account to a verified signature.
- [Encryption guide](/guides/encryption) — the encrypt/decrypt round trip.
- [Architecture](/architecture/index) — how the TEE, the chain, and IPFS establish trust.
46 changes: 26 additions & 20 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,23 @@
{
"tab": "Building on Lit Protocol",
"pages": [
"index",
"quickstart",
{
"group": "Management",
"pages": [
"management/dashboard",
"management/api_direct",
"management/account_modes",
"management/pricing",
"management/crypto",
"management/litkey",
"management/api_keys",
"management/errors"
]
"group": "Get Started",
"pages": ["index", "quickstart", "concepts"]
},
{
"group": "Build for Your Use Case",
"pages": ["guides/signing", "guides/encryption"]
},
{
"group": "Lit Actions",
"pages": [
"lit-actions/index",
"lit-actions/imports",
"lit-actions/wasm",
"lit-actions/examples",
"lit-actions/patterns",
"lit-actions/secrets",
"lit-actions/imports",
"lit-actions/wasm",
"lit-actions/limits",
{
"group": "Migration",
Expand All @@ -54,6 +47,19 @@
"lit-triggers/examples"
]
},
{
"group": "Administration",
"pages": [
"management/dashboard",
"management/api_direct",
"management/api_keys",
"management/account_modes",
"management/pricing",
"management/crypto",
"management/litkey",
"management/errors"
]
},
{
"group": "Architecture",
"pages": [
Expand All @@ -66,19 +72,19 @@
{
"group": "Security & Verification",
"pages": [
"architecture/verification/attestation",
"architecture/verification/quick-verify",
"architecture/verification/index",
"architecture/verification/quick-verify",
"architecture/verification/attestation",
"architecture/verification/onchain-kms",
"architecture/verification/upgrade-governance",
"architecture/verification/full-verification",
"architecture/verification/chain-of-trust"
"architecture/verification/chain-of-trust",
"architecture/verification/full-verification"
]
}
]
},
{
"group": "Actions Reference",
"group": "Reference",
"pages": ["lit-actions/chipotle"]
}
]
Expand Down
151 changes: 151 additions & 0 deletions docs/guides/encryption.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
---
title: "Encryption & Decryption"
description: "From a fresh account to an encrypt/decrypt round trip: seal a secret under a vault PKP, store the ciphertext anywhere, and decrypt it only from code you've authorized on-chain."
---

This guide takes you from nothing to a working **encrypt/decrypt round trip**, then shows the pattern nearly every real integration uses it for: keeping API keys and credentials out of your code while still using them inside a Lit Action. Administrative pages are linked where you meet them.

The model in two sentences: `Lit.Actions.Encrypt` seals data with an AES key derived — inside the TEE — from a PKP you own, and that key never exists outside the enclave. So "who can decrypt this?" reduces to "which action code is permitted to use that PKP?", which is on-chain configuration you control.

If PKPs, groups, or usage keys are new to you, keep [Core Concepts](/concepts) open in a second tab. Coming from Lit V1 / Naga access-control conditions? Read [the model comparison](/lit-actions/migration/encryption) first — this is a different (and simpler) approach.

## What you'll build

1. A **vault PKP** — the key your ciphertexts are sealed under.
2. An action that **encrypts** a secret and returns the ciphertext.
3. An action that **decrypts** it — and only runs because you've authorized it.

<Steps>
<Step title="Create an account and add funds">
If you haven't yet, follow the [Quick Start](/quickstart) through funding: create an account (store the account API key — it's shown once) and add at least $5.00 of credits. Metered calls without credits fail with `402 Payment Required`.

<Info>Admin reference: [API Keys](/management/api_keys) · [Pricing](/management/pricing) · [Errors](/management/errors).</Info>
</Step>

<Step title="Mint a vault PKP">
```bash
curl -s -X POST "https://api.chipotle.litprotocol.com/core/v1/create_wallet" \
-H "X-Api-Key: YOUR_ACCOUNT_KEY"
```

The returned `wallet_address` is your `pkpId`. Treat one PKP as one **trust boundary**: everything encrypted under it is decryptable by any action allowed to use it. Separate app secrets from user data by minting separate PKPs — see [PKP wallets as data vaults](/lit-actions/patterns#encrypt--decrypt--pkp-wallets-as-data-vaults).
</Step>

<Step title="Encrypt a secret">
<Tabs>
<Tab title="JavaScript (Core SDK)">
```javascript
const result = await client.litAction({
apiKey: accountApiKey,
Comment on lines +38 to +39
code: `
async function main({ pkpId, secret }) {
const ciphertext = await Lit.Actions.Encrypt({ pkpId, message: secret });
return { ciphertext };
}
`,
jsParams: { pkpId: '0xYOUR_VAULT_PKP', secret: 'my-api-key-or-anything' }
});
console.log(result.response);
```
</Tab>
<Tab title="cURL">
```bash
curl -s -X POST "https://api.chipotle.litprotocol.com/core/v1/lit_action" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_ACCOUNT_KEY" \
-d '{
"code": "async function main({ pkpId, secret }) { const ciphertext = await Lit.Actions.Encrypt({ pkpId, message: secret }); return { ciphertext }; }",
"js_params": { "pkpId": "0xYOUR_VAULT_PKP", "secret": "my-api-key-or-anything" }
}'
```
</Tab>
</Tabs>

<Warning>
The plaintext transits this one request (over TLS, into the TEE). For most secrets that's fine — encrypt once from a trusted machine. Never log it, and never leave it in shell history you don't control.
</Warning>
</Step>

<Step title="Store the ciphertext — anywhere">
The ciphertext is safe to store in public: a database, IPFS, a smart contract, an environment variable, even hardcoded in client code. Its confidentiality comes from the PKP-derived key in the TEE, not from where it sits. Storage trade-offs are covered in [Secrets → where to put the ciphertext](/lit-actions/secrets).
</Step>

<Step title="Decrypt it back">
```javascript
const result = await client.litAction({
apiKey: accountApiKey,
Comment on lines +75 to +76
code: `
async function main({ pkpId, ciphertext }) {
const plaintext = await Lit.Actions.Decrypt({ pkpId, ciphertext });
return { plaintext };
}
`,
jsParams: { pkpId: '0xYOUR_VAULT_PKP', ciphertext: 'FROM_STEP_3' }
});
```

Round trip complete. In production you'll rarely *return* the plaintext like this — you'll **use** it inside the action (call an API with it, sign with it) and return only the result. Returning or logging decrypted secrets is the #1 leak path; see [common mistakes](/lit-actions/secrets#common-mistakes).
</Step>

<Step title="Production posture: lock down who can decrypt">
Right now your account key can run any code against the vault PKP. Locking it down is on-chain configuration:

1. **Pin the decrypt action.** Publish your decrypting action to IPFS, [register the CID in a group](/management/dashboard#5-register-ipfs-cids-actions), and add the vault PKP to the same group. Now only that exact code can ever produce the plaintext.
2. **Scope a usage key** to `execute_in_groups: [thatGroup]` for whatever service calls it — it can trigger the decrypt flow but can't run arbitrary code against your vault.
3. **Gate inside the action** if you need caller-level rules: token balance, NFT ownership, a caller signature. The gate is plain JavaScript — see [gating logic](/lit-actions/patterns#gating-logic--aka-access-control-conditions).

The enforcement split — what the chain guarantees vs. what your action code guarantees — is summarized in [Secrets → what's enforced where](/lit-actions/secrets#whats-enforced-where).

<Info>Admin reference: [API Keys](/management/api_keys) · [Groups](/architecture/groups) · [Dashboard](/management/dashboard).</Info>
</Step>
</Steps>

## The pattern you'll actually use: encrypted secrets inside actions

Most encryption use on Lit isn't "encrypt user files" — it's **giving your action a secret without giving it to anyone else**. Encrypt an API key once, pass the ciphertext as a `js_params` value (or hardcode it in the action), and decrypt at runtime inside the TEE:

```javascript
// js_params: { pkpId, city, encryptedWeatherApiKey }
async function main({ pkpId, city, encryptedWeatherApiKey }) {
const apiKey = await Lit.Actions.Decrypt({ pkpId, ciphertext: encryptedWeatherApiKey });
const res = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`
);
const { main: weather } = await res.json();
return { temp: weather.temp }; // the key itself never leaves the enclave
}
```

The same shape secures paid RPC URLs, database connection strings, LLM API keys, and webhook signing secrets. Full treatment: [Secrets](/lit-actions/secrets) and [securing RPC URLs](/lit-actions/patterns#securing-rpc-urls--hiding-api-keys-with-encryption).

## Where encryption goes from here

<CardGroup cols={2}>
<Card title="Secrets lifecycle" icon="vault" href="/lit-actions/secrets">
The full vault model: minting, permitting, storing, bundling multiple secrets, and rotation.
</Card>
<Card title="Token-gated decryption" icon="ticket" href="/lit-actions/patterns#gating-logic--aka-access-control-conditions">
Decrypt only for callers who hold a token or NFT, or who sign a challenge — gates written as plain JS.
</Card>
<Card title="Coming from Lit V1?" icon="route" href="/lit-actions/migration/encryption">
How PKP-derived encryption differs from BLS access-control conditions, and when to use each.
</Card>
<Card title="SDK reference" icon="book" href="/lit-actions/chipotle">
Exact signatures for Lit.Actions.Encrypt and Lit.Actions.Decrypt.
</Card>
<Card title="Encrypted state at scale" icon="database" href="/lit-actions/examples#15-confidential-dark-pool-encrypted-orders-matched-in-the-enclave">
A dark pool that stores every order as ciphertext in Postgres and matches batches inside the enclave.
</Card>
<Card title="Combine with signing" icon="signature" href="/guides/signing">
Decrypt a credential, act on it, sign the result — the two primitives compose in one action.
</Card>
</CardGroup>

## Administrative checklist for an encryption app

- [ ] Separate vault PKPs per trust boundary (app secrets vs. user data) — [Patterns](/lit-actions/patterns#multiple-pkps-in-a-single-action--separating-app-secrets-from-user-signing)
- [ ] Decrypt action published to IPFS, CID pinned in a group with the vault PKP — [Dashboard](/management/dashboard#5-register-ipfs-cids-actions)
- [ ] Usage keys scoped to that group only; account key never ships — [API Keys](/management/api_keys)
- [ ] No action returns or logs plaintext secrets — [Common mistakes](/lit-actions/secrets#common-mistakes)
- [ ] Rotation plan: re-encrypt, replace ciphertext, invalidate the old secret upstream — [Secrets](/lit-actions/secrets)
- [ ] Credit balance monitored; decrypt calls are metered like any action — [Pricing](/management/pricing)
Loading
Loading