# Tutorial 13: Anonymous Message Board

> Steps to design and implement a semi-anonymous messaging protocol.

Canonical URL: https://docs.minaprotocol.com/zkapps/tutorials/anonymous-message-board

This tutorial shows you how to put building zkApp ideas into practice as you walk through designing and implementing of a semi-anonymous messaging protocol.

In this tutorial, you build a smart contract that allows users to publish messages semi-anonymously.
- The contract allows a specific set of users to create new messages but does not disclose which user creates the message.
- This semi-anonymous messaging leverages one aspect of a person's identity without revealing exactly who they are.

An example use case for this semi-anonymous contract is to enable a DAO member to make credible statements on behalf of their DAO without revealing their specific individual identity.

## Prerequisites

Ensure your environment meets the [Prerequisites](/zkapps/tutorials#prerequisites) for zkApp Developer Tutorials.

In particular, make sure you have the zkApp CLI installed:

```sh
$ npm install -g zkapp-cli
```

o1js 3 requires Node.js 22.19.5 or later.

The [example project](https://github.com/MinaProtocol/docs2/tree/main/examples/zkapps/13-anonymous-message-board) uses o1js `^3.0.0`. CI builds it, runs it and runs its tests on every pull request.

## Create the message-board project

1. Create or change to a directory where you have write privileges.
1. Create a project using the `zk project` command:

  ```sh
  $ zk project --ui none message-board
  ```

  The `zk project` command creates a directory with a new project template that is fully set up and ready for local development. Like all zk projects, a git repository is initialized in the project directory. By convention, the `main` branch is the default branch.

1. Change to the project directory:

  ```sh
  cd message-board
  ```

  For this tutorial, you run commands from the root of the `message-board` directory as you work in the `src` directory on files that contain the TypeScript code for the smart contract.

  Each time you make updates, then build or deploy, the TypeScript code is compiled into JavaScript in the `build` directory. See the included [README](https://github.com/MinaProtocol/docs2/blob/main/examples/zkapps/13-anonymous-message-board/README.md) file with usage instructions.

### Prepare the project

Example smart contract files come with the new project, you can delete them if you want.

1. Optional: Delete the example files not used in this tutorial:

  ```sh
  $ rm -rf src/*
  ```

1. To create the `src/message.ts` file:

  ```sh
  $ zk file message
  ```

1. Copy the entire contents of the [message.ts](https://github.com/MinaProtocol/docs2/blob/main/examples/zkapps/13-anonymous-message-board/src/message.ts) example file into your local `message-board/src/message.ts` file.

  ```ts title="src/message.ts"
  import {
    Field,
    SmartContract,
    state,
    State,
    method,
    PrivateKey,
    PublicKey,
    Poseidon,
  } from 'o1js';

  // These private keys are exported so that experimenting with the contract is
  // easy. Three of them (the Bobs) are used when the contract is deployed to
  // generate the public keys that are allowed to post new messages. Jack's key
  // is never added to the contract. So he won't be able to add new messages. In
  // real life, we would only use the Bobs' public keys to configure the contract,
  // and only they would know their private keys.

  export const users = {
    Bob: PrivateKey.fromBase58(
      'EKFAdBGSSXrBbaCVqy4YjwWHoGEnsqYRQTqz227Eb5bzMx2bWu3F'
    ),
    SuperBob: PrivateKey.fromBase58(
      'EKEitxmNYYMCyumtKr8xi1yPpY3Bq6RZTEQsozu2gGf44cNxowmg'
    ),
    MegaBob: PrivateKey.fromBase58(
      'EKE9qUDcfqf6Gx9z6CNuuDYPe4XQQPzFBCfduck2X4PeFQJkhXtt'
    ), // This one says duck in it :)
    Jack: PrivateKey.fromBase58(
      'EKFS9v8wxyrrEGfec4HXycCC2nH7xf79PtQorLXXsut9WUrav4Nw'
    ),
  };

  export class Message extends SmartContract {
    // On-chain state definitions
    @state(Field) message = State<Field>();
    @state(Field) messageHistoryHash = State<Field>();
    @state(PublicKey) user1 = State<PublicKey>();
    @state(PublicKey) user2 = State<PublicKey>();
    @state(PublicKey) user3 = State<PublicKey>();

    init() {
      // Reset the whole state first. Without this the zkApp never gains the
      // `provedState === true` status, which is what certifies that the state
      // was produced by proofs rather than set by the developer.
      super.init();

      // Define initial values of on-chain state
      this.user1.set(users['Bob'].toPublicKey());
      this.user2.set(users['SuperBob'].toPublicKey());
      this.user3.set(users['MegaBob'].toPublicKey());
      this.message.set(Field(0));
      this.messageHistoryHash.set(Field(0));
    }

    @method async publishMessage(message: Field, signerPrivateKey: PrivateKey) {
      // Compute signerPublicKey from signerPrivateKey argument
      const signerPublicKey = signerPrivateKey.toPublicKey();

      // Get approved public keys
      const user1 = this.user1.getAndRequireEquals();
      const user2 = this.user2.getAndRequireEquals();
      const user3 = this.user3.getAndRequireEquals();

      // Assert that signerPublicKey is one of the approved public keys
      signerPublicKey
        .equals(user1)
        .or(signerPublicKey.equals(user2))
        .or(signerPublicKey.equals(user3))
        .assertTrue();

      // Update on-chain message state
      this.message.set(message);

      // Compute new messageHistoryHash
      const oldHash = this.messageHistoryHash.getAndRequireEquals();
      const newHash = Poseidon.hash([message, oldHash]);

      // Update on-chain messageHistoryHash
      this.messageHistoryHash.set(newHash);
    }
  }
  ```

This code serves as the scaffolding for the rest of the tutorial and contains a smart contract called `Message` with two methods:

- `init()` - Similar to the `constructor` in Solidity, it's where you define any set up that needs to happen before users begin interacting with the contract.
- `publishMessage()` - The method that users invoke when they want to create a new message.

  The `@method` decorator tells o1js to:

  - Allow users to call this method
  - Generate a zero knowledge proof (ZKP) of its execution

### Define on-chain state

Every Mina smart contract includes 32 on-chain state variables that each store almost 256 bits of information. In more complex smart contracts, these state variables can store commitments to off-chain storage (for example, commitments for the hash of a file, the root of a Merkle tree, and so on).

For simplicity, this tutorial stores everything on-chain.

:::note
General purpose off-chain storage libraries are appropriate only for development. See [Tutorial 6: Off-Chain Storage](/zkapps/tutorials/offchain-storage).
:::

In this smart contract, one state variable stores the last message. Another stores the hash of all the previous messages so a frontend can validate message history. Three more state variables can store user public keys.

It's possible to store additional public keys by Merkelizing them, but for simplicity this tutorial uses only three keys:

```ts title="src/message.ts"
export class Message extends SmartContract {
  // On-chain state definitions
  @state(Field) message = State<Field>();
  @state(Field) messageHistoryHash = State<Field>();
  @state(PublicKey) user1 = State<PublicKey>();
  @state(PublicKey) user2 = State<PublicKey>();
  @state(PublicKey) user3 = State<PublicKey>();
```

The `@state(Field)` decorator tells o1js that the variable is stored on-chain as a `Field` type.

For practical purposes, the `Field` type is similar to the `uint256` type in Solidity. It can store large integers and addition, subtraction, and multiplication all work as expected. The only caveats are division and what happens in the event of an overflow. To learn more about finite fields, see [Finite field arithmetic](https://en.wikipedia.org/wiki/Finite_field_arithmetic). It is not required to understand exactly how field arithmetic works for this tutorial.

o1js also provides `UInt32`, `UInt64`, and `Int64` types. All o1js types are composed of the `Field` type, including `PublicKey` as shown in the previous example.

### Define the `init()` method

The `init` method is similar to the `constructor` in Solidity. It's where you define any setup that needs to happen before users begin interacting with the contract. In this case, set the public keys of users who can post and initialize `message` and `messageHistoryHash` as zero. The front end interprets the zero value to mean that no messages have been posted yet.

```ts title="src/message.ts"
init() {
  // Reset the whole state first. Without this the zkApp never gains the
  // `provedState === true` status, which is what certifies that the state
  // was produced by proofs rather than set by the developer.
  super.init();

  // Define initial values of on-chain state
  this.user1.set(users['Bob'].toPublicKey());
  this.user2.set(users['SuperBob'].toPublicKey());
  this.user3.set(users['MegaBob'].toPublicKey());
  this.message.set(Field(0));
  this.messageHistoryHash.set(Field(0));
}
```

Start with `super.init()`. It resets every state field to its default, and
without it o1js warns that the zkApp will lack the `provedState === true`
status — the status that certifies the state was produced by proofs rather than
set by the developer. Your own values go after it.

### Define `publishMessage()`

The `publishMessage` method allows an approved user to publish a message. The `@method` decorator makes this method callable by users so that they can interact with the smart contract.

For this example, pass in `message` and `signerPrivateKey` arguments to check that the user holds a private key associated with one of the three on-chain public keys before allowing them to update the message:

```ts title="src/message.ts"
@method async publishMessage(message: Field, signerPrivateKey: PrivateKey) {
```

Note that all inputs are private by default and exist only on the user's local machine when the smart contract runs. The Mina network never sees private inputs.

The smart contract sends only values that are stored as state to the Mina blockchain. This means that even though the value of the `message` argument is eventually public, the value of `signerPrivateKey` never leaves the user's machine as a result of interacting with the smart contract.

### Compute `signerPublicKey` from `signerPrivateKey`

Now that you have the user's private key, you need to derive the associated public key to check it against the list of approved publishers. The `PrivateKey` type in o1js includes a `toPublicKey()` method:

```ts title="src/message.ts"
// Compute signerPublicKey from signerPrivateKey argument
const signerPublicKey = signerPrivateKey.toPublicKey();
```

To check if this public key matches one of the keys stored on-chain, read the three approved keys:

```ts title="src/message.ts"
// Get approved public keys
const user1 = this.user1.getAndRequireEquals();
const user2 = this.user2.getAndRequireEquals();
const user3 = this.user3.getAndRequireEquals();
```

The `getAndRequireEquals()` method reads each value from the on-chain state of the zkApp account. It also adds a precondition to the transaction: the network accepts the transaction only if the on-chain value is still the value that the proof used. If you use `get()` without a precondition, o1js shows a warning, because a proof made with an old value could then change the state.

:::note
o1js uses a single network request to retrieve all on-chain state values simultaneously.
:::

Finally, check if `signerPublicKey` is equal to one of the allowed public keys contained in the `user` variables:

```ts title="src/message.ts"
// Assert that signerPublicKey is one of the approved public keys
signerPublicKey
  .equals(user1)
  .or(signerPublicKey.equals(user2))
  .or(signerPublicKey.equals(user3))
  .assertTrue();
```

Notice the `equals()` and `or()` methods are used instead of the JavaScript operators (`===`, and `||`). `equals()` and `or()` return a `Bool`, and `assertTrue()` makes the proof fail if that `Bool` is false. The built-in o1js methods have the same effect, but they work with o1js types and their execution can be verified using a zero knowledge proof.

`assertTrue()` at the end means that a valid proof is not generated unless `signerPublicKey` is equal to one of the pre-approved users. The Mina network rejects any transaction sent to a zkApp account that doesn't include a valid zero knowledge proof for that account. So it is impossible for users to post new messages unless they have a private key associated with one of the three pre-approved public keys.

### Update `message`

Up to this point, the contract ensures that only approved users can call `publishMessage()`. When they do, the contract updates the on-chain `message` variable to their new message:

```ts title="src/message.ts"
// Update on-chain message state
this.message.set(message);
```

The `set()` method asks the Mina nodes to update the value of their on-chain state, but only if the associated proof is valid.

### Update `messageHistoryHash`

There's one more thing to do. If you want users to be able to keep track of what has been said, then you need to store a commitment to the message history on-chain.

There are a few ways to do this, but the simplest way is to store a hash of your new `message` and your old `messageHistoryHash` every time you call `publishMessage`:

```ts title="src/message.ts"
// Compute new messageHistoryHash
const oldHash = this.messageHistoryHash.getAndRequireEquals();
const newHash = Poseidon.hash([message, oldHash]);

// Update on-chain messageHistoryHash
this.messageHistoryHash.set(newHash);
```

That's it! Save the file. Now, to make sure everything compiles:

```sh
$ npm run build
```

If everything is correct, you see a new `./build` directory where the compiled version of your project lives that you can import into a user interface.

## Deploy and run it

The tutorial above builds the contract. To see it work end to end, the example
in this repository deploys it to a local blockchain and posts to it — no
network, no faucet and no funded accounts:

```sh
cd examples/zkapps/13-anonymous-message-board
npm install
npm start
```

```text
deploying the message board...
approved posters:
  user1: B62qkCkKQ5vSRkTQyEEj22vnHH4hVKfp4pc3QYd4GPSv7u6S4aBE4Fx
  user2: B62qrvaafH1F812QFRF1Us8FjouuetZjU5ZpUUYEBBBA9tG9LogYuRB
  user3: B62qkF6VsNiacd4WHjBpgX7Z4riBsjqbHYK5Z295t9yE3krK74sLYx9
message: 0

Bob publishes a message...
message: 42
history hash: 19021527552571990502182546635536113945580344348338130036510755963756352285063

SuperBob publishes another...
message: 99
history hash: 17709100552276219157770164754333945343219873229290323097995828490618509796710
history hash is the chain of both messages: true

Jack tries to publish...
rejected, as it should be
message is still: 99
```

The deployment is an ordinary zkApp deployment — the account creation fee, the
verification key, and `init()` running as part of it, exactly as in
[Tutorial 3](deploying-to-a-network):

```ts title="src/main.ts"
const deployTx = await Mina.transaction(deployer, async () => {
  AccountUpdate.fundNewAccount(deployer);
  await zkApp.deploy();
});
await deployTx.prove();
await deployTx.sign([deployer.key, zkAppKey]).send();
```

`npm test` runs the same flow as assertions, including the two cases worth
proving rather than describing: the history hash commits to every message in
sequence, and a key the contract does not know is rejected.

## Wrapping up

This tutorial gives you a sense of what's possible with o1js.

The messaging protocol you built is quite simple but also very powerful. You can use this basic pattern to create a whistleblower system, an anonymous NFT project, or even anonymous DAO voting.

The main point is that o1js makes it easy for you to build things that don't intuitively seem possible.

Zero knowledge proofs open the door to an entirely different way of thinking about the internet. We are so excited to see what people like you will build.

Make sure to join the [#zkapps-developers](https://discord.com/channels/484437221055922177/915745847692636181) channel on Mina Protocol Discord.

## Keep going

The logical next steps to extend this project include:

- Allow users to pass signers into the `publishMessage()` method directly so that many different organizations can use a single contract.
  Hint: You'll have to store a commitment to the signers on-chain.
- Allow users to pass an arbitrarily large number of signers into the `publishMessage()` method.
- Store the message history in a Merkle tree so a user interface can quickly check a subset of the messages without evaluating the entire history.
- Build a shiny front end!
