# Dapp entities · Build with zero-knowledge proofs on Chromia · Learn Chromia

> A handful of entities cooperate to make private token transfers possible. Below you'll find what each one stores and the job it performs in the…

Source: https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-entities/

Build with zero-knowledge proofs on ChromiaLesson 12 of 19

1. [01Introduction to the course](https://chromia.com/learn/zero-knowledge-proof/introduction/)
2. [02Zero-knowledge proof](https://chromia.com/learn/zero-knowledge-proof/zero-knowledge-proof/)
3. [03Project setup](https://chromia.com/learn/zero-knowledge-proof/setup/)
4. [04Architecture overview](https://chromia.com/learn/zero-knowledge-proof/architecture-overview/)

Module 1: Writing Circom circuits

1. [05Overview](https://chromia.com/learn/zero-knowledge-proof/circom-circuits/)
2. [06Circom circuits: introduction](https://chromia.com/learn/zero-knowledge-proof/circom-circuits/circom-circuits-introduction/)
3. [07Circom files overview](https://chromia.com/learn/zero-knowledge-proof/circom-circuits/circom-circuits-project/)
4. [08Circom circuits: compile](https://chromia.com/learn/zero-knowledge-proof/circom-circuits/circom-circuits-compile/)

Module 2 – Dapp

1. [09Overview](https://chromia.com/learn/zero-knowledge-proof/dapp/)
2. [10Dapp: setup and run](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-setup-run/)
3. [11Dapp overview](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-overview/)
4. [12Dapp entities](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-entities/)
5. [13PLONK verification](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-verification/)
6. [14Dapp operations overview](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-operations/)
7. [15Dapp queries overview](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-queries/)

Module 3 – Frontend

1. [16Overview](https://chromia.com/learn/zero-knowledge-proof/frontend/)
2. [17Frontend: setup and run](https://chromia.com/learn/zero-knowledge-proof/frontend/frontend-setup-run/)
3. [18Frontend architecture](https://chromia.com/learn/zero-knowledge-proof/frontend/frontend-explore/)
4. [19Frontend: test](https://chromia.com/learn/zero-knowledge-proof/frontend/frontend-test/)

[Learn](https://chromia.com/learn/)[Build with zero-knowledge proofs on Chromia](https://chromia.com/learn/zero-knowledge-proof/)

12 / 19Module 2 – DappAdvanced

# Dapp entities

A handful of entities cooperate to make private token transfers possible. Below you'll find what each one stores and the job it performs in the privacy-preserving token system.

## Core privacy entities

### `spent_nullifier`

rell/src/zkp\_demo/entities.rell

```
entity spent_nullifier {
    key nullifier_hash: big_integer;
}
```

**Purpose**: Records nullifiers that have already been used, which blocks double-spending in the privacy system.

**How it works**:

* Spending a private token produces a unique nullifier derived from its commitment
* That nullifier is written to `spent_nullifier`, flagging it as consumed
* Every transaction is checked against this entity first to see whether its nullifier is already present
* As a result, a private token can never be spent more than once

### `unspent_commitment`

rell/src/zkp\_demo/entities.rell

```
entity unspent_commitment {
    key commitment_hash: big_integer;
}
```

**Purpose**: Holds every commitment that has not yet been spent, i.e. the private tokens currently available.

**How it works**:

* A private token takes the form of a commitment, which is a cryptographic hash
* Shielding tokens (making them private) or receiving them through a private transfer adds new commitments
* Spending or unshielding tokens deletes the corresponding commitments
* Together, these records serve as the "UTXO set" for private tokens

### `private_transfer_event`

rell/src/zkp\_demo/entities.rell

```
@log
entity private_transfer_event {
    commitment_input: big_integer;
    commitment_output_sender: big_integer;
    commitment_output_recipient: big_integer;
    recipient_private_address: big_integer;
    nullifier: big_integer;
    encrypted_sender_note: byte_array;
    encrypted_recipient_note: byte_array;
}
```

**Purpose**: Keeps a log of every private transfer along with its encrypted details.

**How it works**:

* Writes an on-chain record for each private transfer transaction
* Stores the input commitment being consumed plus two output commitments: one for the sender's change and one for the recipient
* Carries encrypted notes that can be decrypted only by the sender and the recipient
* Because of the `@log` annotation, you can query these records for wallet scanning
* Wallets scan these events to discover private transactions addressed to them

## Token shielding/unshielding entities

### `shield_log`

rell/src/zkp\_demo/private\_token.rell

```
@log
entity shield_log {
    account_id: byte_array;
    commitment: big_integer;
    amount: big_integer;
    encrypted_note: byte_array;
}
```

**Purpose**: Records each conversion of public tokens into private tokens (shielding).

**How it works**:

* Captures the moment a user turns public FT4 tokens into private commitments
* Ties the public account to the private commitment that was produced
* Includes an encrypted note describing the transaction
* Gives users a way to review their shielding history

### `unshield_log`

rell/src/zkp\_demo/private\_token.rell

```
@log
entity unshield_log {
    account_id: byte_array;
    nullifier: big_integer;
    commitment: big_integer;
    amount: big_integer;
}
```

**Purpose**: Records each conversion of private tokens back into public tokens (unshielding).

**How it works**:

* Captures the moment a user turns private commitments back into public FT4 tokens
* Ties the nullifier and commitment to the public account that receives the tokens
* Gives users a way to review their unshielding history
* Supplies an audit trail for regulatory compliance

## User management entity

### `private_address_registry`

rell/src/zkp\_demo/private\_address\_registry.rell

```
entity private_address_registry {
    key account_id: byte_array;
    mutable private_address: big_integer;
    mutable public_encryption_key: text;
    mutable registered_at: timestamp;
}
```

**Purpose**: Associates each public FT4 account with its private address and encryption key.

**How it works**:

* You register a private address, derived from your private key, against your public FT4 account
* Your public encryption key is stored here so notes can be encrypted securely for you
* Senders look up a recipient's private address here before sending private tokens
* In effect, it is a "phone book" for private transactions
* You can update your keys whenever needed

## Privacy model summary

Taken together, these entities form a **UTXO-based privacy model**:

1. Shielding turns **public tokens** into **private commitments**
2. **Private commitments** move between users privately with the help of zero-knowledge proofs
3. **Nullifiers** stop double-spending while keeping the spent commitment hidden
4. **Encrypted notes** let parties exchange transaction details securely
5. The **address registry** makes it possible for users to receive private payments

You get **transaction privacy**, with amounts and recipients concealed, while **auditability** is preserved through the logged events and **regulatory compliance** is supported by shield/unshield logs that connect to public identities.

Written for this site from Chromia’s public course. Code samples are the course’s own.

[← Previous**Dapp overview**](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-overview/)[Next →**PLONK verification**](https://chromia.com/learn/zero-knowledge-proof/dapp/dapp-verification/)

On this page

* [Core privacy entities](#core-privacy-entities)
* [Token shielding/unshielding entities](#token-shieldingunshielding-entities)
* [User management entity](#user-management-entity)
* [Entity relationships](#entity-relationships)
* [Privacy model summary](#privacy-model-summary)
