This document defines the intended protocol behavior of PrivateDAO as implemented in the current repository.
It is not a proposal for redesign. It is a structured description of the implemented governance lifecycle, commit-reveal semantics, execution rules, and core invariants.
The repository also includes a non-breaking zk stack. That stack is not part of the currently deployed instruction interface and is described explicitly so reviewers do not confuse additive zk work with the live protocol surface.
DAO authority- initializes a DAO
- may cancel an open proposal
- may veto a passed but not-yet-executed proposal during the timelock window
Governance token holder- may create proposals if token ownership requirements are satisfied
- may commit and reveal votes
- may delegate voting power for a single proposal
Delegatee- may consume delegated voting power for the bound proposal
Keeper- may reveal on behalf of a voter if explicitly authorized at commit time
Permissionless finalizer- may finalize once reveal conditions are satisfied
Permissionless executor- may execute once a proposal is passed and timelock conditions are satisfied
DAO- governance token configuration
- quorum and timing configuration
- authority
- proposal counter
Proposal- bound to one DAO
- contains lifecycle state, tallies, timings, and optional treasury action
VoterRecord- bound to one proposal and one voter
- stores commitment, weights, and reveal state
VoteDelegation- bound to one proposal and one delegator
- stores delegatee, delegated weights, and
is_used
Treasury PDA- bound to one DAO
- executes approved treasury actions
VoterWeightRecord- Realms-style voter-weight compatibility account
Implemented status values:
VotingPassedFailedCancelledVetoed
Additional operational phases derived from state plus timestamps:
Commit window openReveal window openExecutableExecuted
Votingwithnow < voting_end- commit phase
Votingwithvoting_end <= now < reveal_end- reveal phase
Passedwithnow < execution_unlocks_atandis_executed = false- timelocked, vetoable phase
Passedwithnow >= execution_unlocks_atandis_executed = false- executable phase
Passedwithis_executed = true- executed terminal state
- DAO initialization
- no proposal state transition
Voting->Cancelled- via
cancel_proposal
- via
Voting->Passed- via
finalize_proposalafter reveal window closes and pass conditions are satisfied
- via
Voting->Failed- via
finalize_proposalafter reveal window closes and pass conditions are not satisfied
- via
Passed->Vetoed- via
veto_proposalduring timelock and before execution
- via
Passedwithis_executed = false->Passedwithis_executed = true- via
execute_proposalafter unlock
- via
- commit after commit window closes
- reveal before commit
- reveal before voting end
- reveal after reveal window closes
- finalize before reveal window closes
- finalize from non-
Votingstatus - execute before finalize
- execute before timelock unlock
- execute twice
- veto after execution
- veto after timelock expiry
- regression from finalized/cancelled/vetoed states back to
Voting
Commitment preimage:
sha256(vote_byte || salt_32 || proposal_pubkey_32 || voter_pubkey_32)
Where:
vote_byteis1for yes and0for nosalt_32is a 32-byte random secretproposal_pubkey_32is the target proposal public keyvoter_pubkey_32is the revealing voter identity
For direct commit:
- proposal must be in
Voting - current time must be before
voting_end - voter token account must belong to signer
- voter token mint must equal DAO governance mint
- voter token balance must be positive
- if
governance_token_required > 0, balance must satisfy that threshold - voter record must not already be committed
- delegation marker for the same proposal/voter must not already exist
For delegated commit:
- all direct timing/state checks still apply
- delegation must be bound to the same proposal
- delegation delegatee must equal signer
- delegation must not already be used
- direct-vote marker for the delegator must not already exist
- proposal must still be in
Voting - current time must be at or after
voting_end - current time must be before
reveal_end - voter record must already be committed
- voter record must not already be revealed
- signer must be either:
- the bound voter
- the bound keeper (
voter_reveal_authority)
- recomputed commitment must equal stored commitment
Replay resistance is provided by:
- one voter record per proposal/voter pair
has_committedhas_revealed- commitment bound to proposal public key and voter public key
- delegation
is_used - lifecycle timing gates
The current raw commitment preimage is:
sha256(vote_byte || salt_32 || proposal_pubkey_32 || voter_pubkey_32)
Proposal scoping is therefore enforced both cryptographically and through the proposal-bound VoteRecord PDA, reveal account binding, and lifecycle flags on the stored voter record.
The repository now includes an additive zero-knowledge layer that does not change the deployed protocol semantics.
Current live protocol:
- commit uses
sha256(vote_byte || salt_32 || proposal_pubkey_32 || voter_pubkey_32) - reveal checks the committed preimage
Current zk stack:
- proves boolean vote form and minimum-weight eligibility
- proves delegation activation and delegatee binding
- proves weighted tally integrity over a commitment-consistent reveal sample
- proves proposal-scoped nullifier bindings across vote, delegation, and tally layers
This stack is currently:
- off-chain
- verifier-backed through Groth16
- intentionally non-breaking
It is an upgrade path, not a claim that the deployed program already verifies zk proofs on-chain.
- current time must be at or after
reveal_end - proposal status must still be
Voting - proposal must be bound to the supplied DAO
Quorum condition:
commit_count > 0reveal_count / commit_countmust satisfy DAO quorum percentage
Voting mode conditions:
TokenWeightedyes_capital > no_capital
Quadraticyes_community > no_community- assumes an external sybil-resistance or identity policy
DualChamber- both capital and community thresholds must be satisfied
If passed:
- status becomes
Passed execution_unlocks_at = now + execution_delay_seconds
If failed:
- status becomes
Failed execution_unlocks_atremains unchanged from the default non-executable value unless previously impossible under valid flow
- proposal status must be
Passed - proposal must not already be executed
- current time must be at or after
execution_unlocks_at - proposal must be bound to DAO
- treasury PDA must be bound to DAO
Execution requires:
treasury_recipient == action.recipient
Effect:
- lamports move from treasury PDA to configured recipient
Execution requires:
treasury_recipient == action.recipient- action contains token mint
- source and destination token accounts are owned by the token program
- token account data length sanity checks pass
- treasury token owner equals treasury PDA
- treasury token mint equals configured action mint
- recipient token mint equals configured action mint
- source and destination token accounts are not the same account
- recipient token owner equals configured recipient
Effect:
- SPL token amount moves from treasury token account to configured recipient token account
The current implementation intentionally rejects CustomCPI rather than exposing an event-only success path or arbitrary CPI execution.
Effect:
- proposal creation or execution that attempts
CustomCPIis rejected - no treasury movement or execution flag mutation occurs
- the live protocol does not claim arbitrary CPI execution inside the PrivateDAO program
initialize_dao- requires DAO authority signer
create_proposal- requires proposer signer with governance token ownership
cancel_proposal- authority only
veto_proposal- authority only
commit_vote- voter signer only
delegate_vote- delegator signer only
commit_delegated_vote- delegatee signer only
reveal_vote- voter or authorized keeper only
finalize_proposal- permissionless
execute_proposal- permissionless
Keeper note:
- keeper authority is optional and proposal-scoped
- keeper can only submit the exact reveal for the stored commitment
- successful reveal clears stored keeper authority from the voter record
Executed => status == PassedExecuted => execution_unlocks_at > 0Cancelled => not executableVetoed => not executableFailed => not executable- finalized proposals do not return to
Voting
has_revealed => has_committed- reveal count never exceeds commit count
- invalid reveal does not mutate tally
- revealed tally only comes from valid commitments
- proposal-scoped replay protection is enforced both by proposal-bound commitments and by voter-record PDA binding
- successful execute is the only valid path that sets
is_executed = true - failed execute does not set
is_executed - duplicate execute does not create duplicate treasury effects
- treasury balance delta must equal intended action amount on successful execute
- reveal rebate is only paid when the proposal account remains rent-safe; it is not a treasury payout
- proposal belongs to exactly one DAO
- voter record belongs to exactly one proposal and one voter
- delegation belongs to exactly one proposal and one delegator
- treasury belongs to exactly one DAO
- execution accounts must be exact, not approximate or merely initialized
- proof public signals are proposal-bound
- proof public signals are DAO-bound
- nullifier is scoped to voter, proposal, and DAO
- eligibility proof is scoped to voter, weight, and DAO
- proof verification alone does not advance on-chain lifecycle state
- strict ZK finalization requires
ProposalExecutionPolicySnapshot - strict ZK finalization requires
ProposalProofVerification.status == Verified - strict ZK proof records must be DAO-bound and proposal-bound
- strict ZK proof records must match the canonical V2 proposal payload hash
- strict ZK proof records must be fresh at finalization time
- threshold-attested proof verification is an explicit on-chain trust model, not a cryptographic verifier CPI claim
- strict proof companion accounts cannot be overwritten with a different strict payload after recording
- strict confidential payout execution requires
SettlementEvidence.status == Verified - strict settlement evidence must be DAO-bound, proposal-bound, payout-plan-bound, and canonical-payout-field-bound
- strict settlement evidence must be fresh at execution time
- strict settlement evidence is consumed through a single-use
SettlementConsumptionRecordPDA - strict settlement evidence accounts cannot be overwritten with a different evidence payload after recording
- the same settlement evidence cannot execute two payouts
- legacy objects remain readable after V2 policy accounts are added
DaoSecurityPolicyinitialization is idempotent only for the exact same configuration- DAO security policy updates are monotonic and cannot roll back to weaker enforcement
- DAO-wide future policy changes do not silently reinterpret proposals with existing
ProposalExecutionPolicySnapshot - proposal policy snapshots cannot be overwritten with a different policy after recording
- strict enforcement requires explicit companion accounts rather than implicit reinterpretation of legacy records
- lifecycle gating
- signer requirements
- PDA/account binding
- treasury execution checks
- tally accounting
- delegation consumption
- CLI and frontend orchestration
- salt storage
- explorer linking
- devnet/local validator setup
- zk witness generation
- zk proving
- primary Groth16 proof verification workflow
- this specification reflects the current repository behavior, not an aspirational redesign
- commit-reveal hides vote content, not timing metadata
- Groth16 witness generation and proving remain off-chain. Legacy
zk_enforcedreceipts are retained for compatibility, while the V2 strict path adds companion proof-verification accounts, threshold-attested verification, canonical payload binding, expiry, and object-level policy snapshots. This is stronger than legacy receipt metadata, but still an attested fallback until a cryptographic verifier CPI is integrated. - no external audit is claimed by this document