Midnight logoMidnight Academy
← back to projects

Project 04

Messaging

Voting taught you to keep a choice private while disclosing an aggregate. Messaging asks a different question: how do you keep an entire piece of content private, forever, while still proving on-chain that something was sent?

What you'll learn

The contract

Save this as contracts/messaging.compact:

pragma language_version >= 0.23;

import CompactStandardLibrary;

witness getMessageContent(): Opaque<"string">;

export ledger messageCount: Uint<32>;

export circuit sendMessage(): [] {
    const content = getMessageContent();
    messageCount = messageCount + 1;
}

The important part is what's missing

Look closely: content is read from the witness, but never disclosed anywhere. It's used inside the circuit and then simply not written to ledger state. That's the whole trick: private data doesn't need a special "hide me" instruction, it just needs you to never call disclose() on it. The chain only ever learns that a message-sending event happened, via the incremented counter, never what was said.

Where a real version goes further

A production messaging app would also need actual encrypted delivery to a specific recipient (so only they can read the content off-chain), not just "nobody on-chain sees it." That's a real cryptography problem beyond this exercise, worth researching once this pattern feels natural.

Build it yourself

compact compile contracts/messaging.compact contracts/managed/messaging

Deploy, call sendMessage() a few times with different witness content each time, and confirm messageCount goes up correctly, while nothing about the actual message text appears anywhere in the ledger state you read back.

Next: Project 05 — Identity →