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 difference between hiding a value from the chain and hiding it from everyone except the recipient
- Why a message's content should usually live in private state, not be disclosed at all
- Using a public counter as proof-of-activity without proof-of-content
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.