๐๏ธ Chapter 6: Upgrade to a DAO
The Builder's Journey -- Chapter 6 of 6
Chapter 1: you proved you know a secret. Chapter 6: you prove you hold coins, cast a vote, and govern a protocol.
Same primitive. Same three lines. divine(), hash(), assert().
The secret in Chapter 1 was a password. The secret here is your coin balance, your identity, and your vote -- all hidden, all proven.
๐ก The Governance Problem
On transparent chains, votes are public -- enabling vote buying, social coercion, and whale tracking. Private voting fixes all three: the vote is proven valid without revealing who cast it or in which direction.
๐ฏ What We Are Governing
The name service from Chapter 3. Specifically: coin holders from Chapter 2
vote on whether to change a name's resolver. A proposal says "change the
resolver of name X from old_key to new_key." If the vote passes, the
name's metadata hash is updated.
The coin from Chapter 2 becomes a governance token. No new token needed. If you hold PLUMB coins, you can vote. Your weight equals your balance.
๐ The Vote Program
Create a file called vote.tri:
program vote
fn main() {
// --- Public inputs: what we are voting on ---
let proposal_hash: Digest = pub_read5()
let coin_root: Digest = pub_read5()
let nullifier_root: Digest = pub_read5()
// --- Secret inputs: the voter's coin account ---
let voter_id: Field = divine()
let voter_bal: Field = divine()
let voter_nonce: Field = divine()
let voter_auth: Field = divine()
let voter_lock: Field = divine()
// Reconstruct the voter's leaf in the coin tree
let leaf: Digest = hash(
voter_id, voter_bal, voter_nonce,
voter_auth, voter_lock, 0, 0, 0, 0, 0
)
// Verify the leaf exists in the coin Merkle tree
let depth: U32 = 32
let leaf_index: U32 = as_u32(divine())
let mut idx: U32 = leaf_index
let mut current: Digest = leaf
for _ in 0..depth bounded 64 {
(idx, current) = merkle_step(idx, current)
}
assert_digest(current, coin_root)
// Prove voter identity -- same pattern as Chapter 1
let auth_secret: Field = divine()
let auth_hash: Digest = hash(
auth_secret, 0, 0, 0, 0, 0, 0, 0, 0, 0
)
assert_eq(auth_hash[0], voter_auth)
// The vote: 1 = yes, 0 = no (secret)
let vote_dir: Field = divine()
assert(vote_dir == 0 + (vote_dir == 1))
// Weight = balance (coin-weighted voting)
let yes_weight: Field = voter_bal * vote_dir
let no_weight: Field = voter_bal * sub(1, vote_dir)
// Nullifier prevents double-voting on this proposal
let proposal_field: Field = proposal_hash[0]
let nullifier: Digest = hash(
voter_id, voter_nonce, proposal_field,
0, 0, 0, 0, 0, 0, 0
)
seal VoteNullifier {
id: voter_id,
nonce: voter_nonce,
proposal: proposal_field
}
// Public output: yes weight and no weight
pub_write(yes_weight)
pub_write(no_weight)
}
event VoteNullifier {
id: Field,
nonce: Field,
proposal: Field
}
Forty-five lines. A complete private vote.
๐ What the Proof Proves
The verifier receives a proof and two public outputs: yes_weight and
no_weight. That is all.
The verifier does not see:
- Who voted.
voter_idis divine -- it never leaves the prover. - Their total balance.
voter_balis divine. The verifier knows the voter has at least as many coins as the weight, but not the exact amount. - Which direction. Both
yes_weightandno_weightare published. One is zero and the other is the balance -- but the verifier cannot link this to a specific voter, so it reveals nothing about any individual.
The verifier does confirm:
- The voter's leaf exists in the coin tree (Merkle proof against
coin_root). - The voter knows the auth secret for that leaf (hash preimage proof).
- The vote direction is exactly 0 or 1 (not a fabricated value).
- The weight equals the voter's actual balance (not inflated).
- The nullifier is correctly derived (no double voting).
One proof. Five guarantees. Zero information about the voter.
๐ The Tally
After all votes are submitted, the tallier sums the results:
program tally
fn main() {
let num_votes: U32 = as_u32(pub_read())
let mut total_yes: Field = 0
let mut total_no: Field = 0
for i in 0..num_votes bounded 1024 {
let yes_w: Field = divine()
let no_w: Field = divine()
total_yes = total_yes + yes_w
total_no = total_no + no_w
}
// The result: did the proposal pass?
let (yes_hi, yes_lo): (U32, U32) = split(total_yes)
let (no_hi, no_lo): (U32, U32) = split(total_no)
let passed: Bool = yes_lo > no_lo
pub_write(total_yes)
pub_write(total_no)
}
The tally program aggregates every individual vote proof's output. Each
voter's yes_weight and no_weight feed into a running sum. The final
totals are published. Anyone can verify the tally proof independently.
In a production system, you would additionally verify each individual vote proof inside the tally using proof composition (Tier 3) -- the tally program would recursively verify that each weight came from a valid vote proof. For this tutorial, the structure is what matters: individual proofs feed a collective result.
โก Executing the Proposal
If the vote passes, the name resolver must update. This is where the chapters compose.
The name service from Chapter 3 has an update operation. The coin from
Chapter 2 provides the governance token. The vote from this chapter
proves the holders decided. The execution links them:
- The vote tally proof proves
total_yes > total_no. - The name update proof changes the resolver from
old_keytonew_key. - The two proofs compose: vote result authorizes the name change.
In Chapter 3, every name has an owner and a metadata hash (the resolver).
The update operation requires authorization. Set the name's update
authority to the DAO -- then only a passing vote can trigger the change.
The composition is:
DAO_vote_tally compose Name_update
One proof that says: the holders voted yes, therefore the resolver changes. No multisig. No admin key. No trusted party. The math authorizes the update.
๐งฉ The Full Circle
Six chapters. Six programs. One primitive: divine(), hash(), assert(). Every program -- coin, name, strategy, auction, vote -- is the Chapter 1 pattern with more context. The secret changes. The primitive never does.
โ What You Built (Complete Application)
| Chapter | Program | Secret | What Is Proven |
|---|---|---|---|
| 1. Prove a Secret | secret.tri |
Password | Knowledge of preimage |
| 2. Build a Coin | coin.tri |
Account auth key | Valid state transition |
| 3. Build a Name | name.tri |
Name ownership key | Name ownership + resolver |
| 4. Build a Strategy | strategy.tri |
Reserve position | Invariant holds (x * y = k) |
| 5. Auction Names | auction.tri |
Bid amount | Bid >= second price |
| 6. Upgrade to a DAO | vote.tri |
Balance + identity + vote | Valid weighted vote |
Six programs. Six secrets. One primitive.
You have a liquid DAO where coin holders privately govern their name service. The coin is the governance token. The vote is hidden. The result is public. The resolver updates when the math says it should.
The first line of Chapter 1: "You are about to learn the most powerful primitive in cryptography."
You just used it to build a private, quantum-safe DAO.
Same primitive. Same three lines.
๐ฎ What Is Next
You have built a complete private web3 application. To go further:
- Language Tour -- complete syntax reference with examples
- Agent Briefing -- compact cheat-sheet for the full language
- Language Reference -- sponge, Merkle, extension fields, proof composition
- Gold Standard -- production token standards and capability library
- OS Reference -- portable runtime APIs across 25 operating systems
os/neptune/standards/-- production implementations ofcoin.triandcard.tri