> ## Documentation Index
> Fetch the complete documentation index at: https://docs.air3.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Iden3 credentials

> Issue and verify Iden3 BJJ credentials with zero-knowledge proofs and optional on-chain verification. Contact the AIR team to enable this option.

<Note>
  Iden3 credentials are an advanced option. **Contact the AIR team to enable them** for your partner account before you start: [Partner with us](https://air3.com/partner-with-us). New integrations should use [SD-JWT VC](/products/identity/credential-formats), which is self-serve.
</Note>

Iden3 credentials use a BJJ\_SIG\_2021 signature and the W3C Verifiable Credentials Data Model v1.1 in JSON-LD form. Holders prove claims with Groth16 zero-knowledge proofs.

## When to use Iden3

Choose Iden3 over SD-JWT when you need one of these:

* **Predicate proofs.** Prove a condition such as `age > 18` without revealing the value.
* **Unlinkable presentations.** Each proof is randomized, so verifiers cannot link a holder's presentations.
* **On-chain verification.** Check a proof through the Universal Verifier and Groth16 verifier contracts.

The cost is heavier proof generation on the holder's device and a verifier that implements the Iden3 proof profile. Generic W3C VC support is not enough. See [Credential formats](/products/identity/credential-formats) for the full comparison.

| Proof type | What it is |
| - | - |
| `BJJ_SIG_2021` | The issuer signs the credential with its BJJ key. Supports off-chain and on-chain verification. |

## Set up the issuer service

Iden3 issuers use the Iden3 branch of the [AIR issuer service](https://github.com/MocaNetwork/air-issuer-service/tree/main_iden3). It exposes the same `POST /available-vc` and `POST /issue-vc` contract as the SD-JWT service, with `proofType: "BJJ_SIG_2021"`.

```bash theme={null}
git clone -b main_iden3 https://github.com/MocaNetwork/air-issuer-service.git
cd air-issuer-service
npm i
```

### Generate the issuer seed

Your BJJ issuer DID is derived from a 32-byte seed:

```bash theme={null}
echo "SEED=0x$(openssl rand -hex 32)"
```

<Warning>
  `SEED` is your issuer identity. Store it in a secret manager and back it up. Losing it means you can no longer act as that issuer, and changing it creates a new issuer DID.
</Warning>

### Configure the environment

| Variable | Purpose |
| - | - |
| `NODE_ENV` | `sandbox` or your production value |
| `DATABASE_URL` | PostgreSQL connection URL |
| `ISSUER_ORIGIN` | Public HTTPS origin of the service, without a trailing slash. Used in credential status URLs |
| `SEED` | 32-byte hex seed for the BJJ issuer identity |
| `PARTNER_ID` | Your AIR Partner ID |
| `PARTNER_PRIVATE_KEY_KID`, `PARTNER_PRIVATE_KEY_ALG`, `PARTNER_PRIVATE_KEY_DER` | Partner JWT signing key, as in the [issuance quickstart](/get-started/quickstarts/issue-credentials#step-2-configure-and-start-the-issuer-backend) |
| `PARTNER_JWKS` | Public JWKS matching the partner key |
| `API_KEY` | Value AIR sends in `x-api-key` |
| `ADMIN_API_KEY` | Value your servers send in `x-admin-api-key` |

### Extract the issuer DID

Start the service. It logs the DID at startup as `Issuer DID: did:air:…`. You can also print it with the REPL:

```bash theme={null}
echo 'console.log(get(CredentialIssuingService).issuerDID.string());' | npm run repl -
```

Give this DID to the AIR team with your Partner ID, API key, and endpoint URLs so they can register and activate the issuer.

### Define a schema class

Create the schema in the Dashboard and record its schema ID, type, schema JSON URL, and JSON-LD context URL. Then add a class under `src/issuer/schemas/`:

```ts src/issuer/schemas/schema-<SCHEMA_ID>.ts theme={null}
import { BaseSchema } from "./base-schema";

export default class Schema extends BaseSchema {
  public readonly schemaId = "<SCHEMA_ID>";
  public readonly schemaType = "<TYPE>";
  public readonly schemaUrl = "https://.../schema";
  public readonly schemaContextUrl = "https://.../ctx";

  async generateCredentialData(userId: string) {
    return {
      credentialSubject: {
        // keys must match the schema; do not set `id`
        someField: "...",
      },
      expiration: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60,
    };
  }
}
```

Register it in `src/issuer/schemas/index.ts`:

```ts src/issuer/schemas/index.ts theme={null}
import Schema1 from "./schema-<SCHEMA_ID>";

const schemas: BaseSchema[] = [new Schema1()];
export default schemas;
```

Claim keys and types must match the Dashboard schema exactly, or merklization and verification fail. The service sets `credentialSubject.id` to the holder DID.

### Issue in bulk without a user session

The Iden3 service includes a CSV runner. It resolves each email through AIR `initialize-user`, issues, encrypts, and uploads:

```csv theme={null}
email,expiration,credentialSubject.field1,credentialSubject.field2
member@example.com,2030-01-01T00:00:00+08:00,"""Hello World""",4
```

Every `credentialSubject.[field]` cell must be a JSON-stringified value, so strings carry quotes.

```bash theme={null}
npm run batch-issue-vc-csv -- \
  <SCHEMA_ID> \
  <SCHEMA_URL> \
  <SCHEMA_TYPE> \
  ./credentials-batch-1.csv
```

The runner writes a result log named `[unix_timestamp_ms].csv`.

### Revocation and status

| Method | Path | Called by | Purpose |
| - | - | - | - |
| `POST` | `/admin/revoke` | Your servers (`x-admin-api-key`) | Revoke by `revocationNonce` |
| `GET` | `/admin/issuance-history` | Your servers (`x-admin-api-key`) | Find issued credentials and their nonces |
| `GET` | `/revocation-status/:nonce` | Verifiers | `{ "isRevoked": boolean }` |
| `GET` | `/credential-status/:nonce` | Verifiers | Non-revocation Merkle proof and issuer tree state (`{ issuer }`) |

The Iden3 service embeds a `/credential-status` URL under `ISSUER_ORIGIN` in each credential, so keep the origin stable. It does not support the token status list.

### Issue both formats

A partner that issues SD-JWT and Iden3 credentials runs both services, each at its own `ISSUER_ORIGIN`. Set `IDEN3_ISSUER_DID` on the SD-JWT service to your BJJ issuer DID; it is then listed under `alsoKnownAs` in your `did:web` document.

## Verify Iden3 credentials

Verification programs for Iden3 credentials can require a zero-knowledge proof and choose where it is checked:

| Use case | Recommended mode |
| - | - |
| Fast eligibility check with no cryptographic proof requirement | Off-chain, no ZKP |
| Privacy-preserving claim proof | Off-chain Sig ZKP |
| Strongest auditability or settlement requirement | Sig ZKP + on-chain verification |

| Mode | Where verification runs | What the app receives |
| - | - | - |
| **Off-chain** | Outside a blockchain transaction, against the program's requirements. | Verification status and the presentation and proof material for the program. |
| **On-chain** | The proof is submitted to the Universal Verifier. Available for `BJJ_SIG_2021` credentials. | The verification outcome and the on-chain transaction information. |

For Iden3 programs, `verifiablePresentation.verifiableCredential` holds W3C Verifiable Credential objects whose `credentialSubject` contains the disclosed claims. `proof` is present only when the program requires a zero-knowledge proof. It carries the Groth16 `proofValue` and `publicSignals`, plus `transactionHash` for on-chain programs. Programs with several ZK queries return one entry per query. See `VerifiablePresentationProof` in the [SDK reference](/api-reference/sdk-reference).

<Warning>
  When an Iden3 program does **not** require a zero-knowledge proof, the presentation has no `proof` block. It is unsigned and carries no cryptographic guarantee; the verification session status is the only trust anchor.
</Warning>

Iden3 credentials and personal data stay off-chain. Depending on the profile, only proofs, verification results, issuer state roots, or revocation commitments are recorded on Moca Chain.

## Related

* [Credential formats](/products/identity/credential-formats)
* [Verifying credentials](/products/identity/verify)
* [Issuance API reference](/api-reference/issuance-api)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.