add motivation and related work

This commit is contained in:
Alain Brenzikofer
2026-08-02 10:31:24 +02:00
parent 8504d608ca
commit 9f3fe2ef2e
2 changed files with 768 additions and 128 deletions
@@ -0,0 +1,83 @@
# Related work for democratic groups: what we borrow, what we reject, and what the literature says we cannot have
Companion to `2026-08-01-group-governance.md`. Each section states a result, then what the governance design does with it.
## 1. Does replacing an admin set require consensus?
This is the question that determines whether the design is possible at all, and there is an exact answer for the object we are building.
Herlihy's consensus hierarchy assigns each shared object a *consensus number*: the largest number of processes for which the object can wait-free implement consensus (Herlihy, "Wait-Free Synchronization", *ACM TOPLAS* 13(1), 1991). Frey, Gestin and Raynal apply it to access control (["The Synchronization Power (Consensus Number) of Access-Control Objects: The Case of AllowList and DenyList"](https://arxiv.org/abs/2302.06344), DISC 2023, doi:10.4230/LIPIcs.DISC.2023.39). Their results:
- **AllowList has consensus number 1** (Corollary 5); it "can be implemented without consensus".
- **k-DenyList has consensus number k** (Corollary 9), where *k* is the number of processes that verify non-membership proofs.
- The two objects are otherwise identical. The entire difference is the **anti-flickering property**: "If the invocation of an operation `op = PROVE(x)` by a correct process is invalid, then any `PROVE(x)` operation that appears after `op` is invalid." The authors note this explicitly: "this difference in term of consensus number is due solely to the anti-flickering property."
This maps onto our object exactly. Promoting members to admin is AllowList-shaped, and needs no consensus. Demoting them is DenyList-shaped, and in our setting *every* member independently verifies admin authority when it receives a role-changing event, so the verifier set is the whole group and `k = N`. A revocation guarantee for the admin set would therefore have consensus number *N*, which in an asynchronous system with faulty processes is unattainable (Fischer, Lynch & Paterson, "Impossibility of Distributed Consensus with One Faulty Process", *JACM* 32(2), 1985).
**What we do with it.** We deliberately decline anti-flickering, which is precisely what keeps the object at consensus number 1. A demoted admin can be re-recognized as admin later: intentionally, by a subsequent referendum, and transiently, by members still inside a contested window. The RFC's "local, time-bounded, revisable finality" is not a shortcoming to be engineered away; it is the exact price of implementability, and this result names the price. Correspondingly, the RFC must never claim revocation semantics ("once demoted, never again recognized"), because that claim would require group-wide consensus.
The same shape appears in Guerraoui, Kuznetsov, Monti, Pavlovič & Seredinschi, ["The Consensus Number of a Cryptocurrency"](https://arxiv.org/abs/1906.05574) (PODC 2019): asset transfer needs no consensus, and a *k*-shared account has consensus number *k*. Our situation is the access-control analogue of that result.
## 2. What consistency model this actually is
Shapiro, Preguiça, Baquero & Zawirski's CRDTs (INRIA RR-7506, 2011) give *strong eventual consistency*: replicas that have received the same updates are in the same state, provided merge is an associative, commutative, idempotent join. Kleppmann & Howard, ["Byzantine Eventual Consistency and the Fundamental Limits of Peer-to-Peer Databases"](https://arxiv.org/abs/2012.00472) (arXiv:2012.00472, 2020) extend this to Byzantine settings via Byzantine causal broadcast, and characterize the boundary in terms of **I-confluence**: invariants that are not I-confluent cannot be maintained without consensus, the canonical example being a uniqueness constraint. Their suggested pattern is to split the application: "an auction could aggregate bids in an I-confluent manner, and only require consensus to decide the winning bid."
Our protocol has that exact shape. Accumulating signed votes is monotonic and I-confluent: vote sets union, and the RFC's annulment rule (conflicting votes from one member cancel that member's vote) is deliberately defined so that union remains order-independent. Deciding *which certificate is in force* is the non-I-confluent part.
**What we do with it.** Where Kleppmann & Howard would reach for consensus on the winner, we substitute a deterministic total order (mandate order, computed from canonical certificate bytes) and accept that members who have seen different certificate sets can disagree until they exchange them. This buys implementability at the cost of agreement, which §1 says is the only trade available.
**Where we fall short of the model, and it is worth fixing.** Strong eventual consistency requires that state be a function of the *set of updates received*. Our ranking obeys this (the RFC pins all three ranking components to canonical bytes precisely so the order is identical at every member), but our *acceptance* does not: whether a certificate is applied depends on the votes a member happens to hold locally, via the union rule at challenge-window close. Two members with the same certificates and different local vote sets can diverge permanently. This is the "knife-edge divergence" the RFC already documents, but the literature frames it more sharply than the RFC does: it is the design's one deviation from SEC, and it is avoidable in principle by making acceptance, like ranking, a pure function of the certificate. That would trade the anti-vote-withholding defence for convergence. The RFC should state this trade explicitly rather than presenting the union rule as free.
## 3. Duelling admins: the specific problem, and why we are not solving it the usual way
Dougal, ["ERA: Epoch-Resolved Arbitration for Duelling Admins in Group Management CRDTs"](https://arxiv.org/abs/2601.22963) (PaPoC '26, doi:10.1145/3806077.3806691), from Element (Matrix), is the closest published work to this problem. It defines the **Duelling Admins** problem (two equally permissioned admins concurrently revoke each other, forming a revocation cycle the merge function must resolve) and observes that "demotion (or revocation) is problematic in these systems because it is a non-monotonic operation", forcing rollbacks of previously authorized events. It surveys how Matrix and Keyhive resolve it, and reports Kleppmann's **seniority ranking** proposal (PaPoC 2025 keynote): execute the more senior admin's operations first. Its critique of seniority is direct: it "prevents a less senior admin from ever revoking a more senior admin", and "a revoked Admin A can retaliate against Admin B by *backdating* a revocation to make it appear as if they concurrently revoked each other", rolling back their own demotion.
The paper's conclusion is that "a Byzantine admin can exploit concurrency to influence the duel, whereby we argue that an **external arbiter is required** to order concurrent events"; ERA introduces a mutually trusted *finality arbiter* peer that periodically publishes signed epoch events, giving a bounded total order and genuine finality.
**What we take from it.** Its critique of seniority ranking is an argument for exactly the change this RFC makes. SimpleX's existing `roleRequiredToChange` *is* seniority ranking (a member may never act on someone of higher role), and it has the flaw the paper identifies: the incumbent hierarchy is unfalsifiable from below. Our design removes the duel rather than arbitrating it: authority for an admin change comes from a majority certificate rather than from another admin, and the action replaces the entire set atomically, so there is no revocation cycle to resolve, only one winner per version under a total order. Its analysis of backdating also corroborates, independently, the weakness our own review found in the witnessed-chain temporal rule: with attacker-chosen timestamps and no arbiter, temporal ordering is a cost-raiser, not a guarantee.
**What we reject, and why.** We do not adopt the finality arbiter. A mutually trusted peer that orders governance events is precisely the chokepoint this RFC exists to remove; an arbiter can stall or reorder a referendum against the incumbents' opponents, recreating the admin-as-infrastructure problem in a new place, and SimpleX p2p groups have no natural candidate for the role. The paper's own fallback options are instructive: it suggests a "Creator" role as arbiter (unacceptable here: the creator is exactly who a democratised group may need to remove) or that "malicious behaviour is socially penalised" (which is the route we take). The consequence must be stated plainly: **we do not get ERA's bounded total order or its improved finality.** We get revisable finality, and the RFC's claims are scoped accordingly.
## 4. How other secure group messengers handle membership
**MLS (RFC 9420)** organizes group state into linear *epochs* advanced by Commit messages, and resolves concurrent commits by having the Delivery Service pick one, a single serialization point. That is a clean solution unavailable to us: SimpleX p2p groups have no server in the message path by construction, and introducing one for governance would reintroduce the censorship surface. Our `govVersion` is an epoch counter without a serializer, which is why we need mandate order where MLS needs only a server.
**DCGKA**, Weidner, Kleppmann, Hugenroth & Beresford, ["Key Agreement for Decentralized Secure Group Messaging with Strong Security Guarantees"](https://doi.org/10.1145/3460120.3484542) (CCS 2021; eprint 2020/1281), is the serverless counterpart, and its treatment of concurrency is directly reassuring for our choice. It states that it does "not impose restrictions on re-adding previous users. In particular, a user may be removed and re-added, possibly indirectly (e.g., due to a remove message 'undoing' a concurrent remove)". That is flickering, accepted in a peer-reviewed decentralized group protocol for the same structural reason §1 gives. DCGKA is also explicitly about *key agreement*, not authorization policy: who is *permitted* to remove whom is left to the application. Our RFC is a proposal for that missing policy layer, not a competitor to DCGKA.
**Signal/WhatsApp group administration**: Rösler, Mainka & Schwenk, ["More is Less: On the End-to-End Security of Group Chats in Signal, WhatsApp, and Threema"](https://eprint.iacr.org/2017/713) (EuroS&P 2018), found that group management messages were not properly authenticated as originating from an actual group member. This is the empirical case for the RFC's recommendation to sign membership events (`XGrpMemNew`/`XGrpMemDel`/`XGrpMemRole`) in governed groups: unauthenticated membership state is not a hypothetical weakness, it is the historically recurring one.
## 5. The voting rule
Adaptive quorum biasing comes from Polkadot; Burdges et al., ["Overview of Polkadot and its Design Considerations"](https://arxiv.org/abs/2005.13456) describes positive turnout bias as generalizing the notion of a quorum, "where additional turnout always makes change more likely, assuming the same yay-to-nay ratio... in case of low turnout we favour the nay side, or status quo, by requiring a super-majority approval, and as turnout approaches 100% the requirement dials down to majority-carries." Its two stated justifications are that "the status quo tends to be safer than any change", and that low-turnout results are volatile: "a result could be 51% 49% one month and then change to 49% 51%", so enacting them is unwise. The comparison is `against/√turnout < approve/√electorate`, our `B²·E < A²·T`. Polkadot's published illustration (25% turnout requires ≈66% approval, 75% turnout requires ≈54%) is consistent with the RFC's integer-exact figures.
**Why this rule rather than a fixed quorum.** Participation quorums are known to invert incentives: opponents of a proposal maximize their chance of defeating it by abstaining rather than voting no, so the rule rewards boycotts and depresses turnout. AQB has no participation threshold, so abstention is never strategically superior to voting nay; a nay always hurts the proposal more than a non-vote. Empirically, fixed quorums would also simply not be met: studies of on-chain governance report very low participation and heavily concentrated voting power (Feichtinger, Fritsch, Vonlanthen & Wattenhofer, ["The Hidden Shortcomings of (D)AOs"](https://arxiv.org/abs/2302.12125); Fritsch, Müller & Wattenhofer, "Analyzing Voting Power in Decentralized Governance: Who controls DAOs?"; Barbereau et al., "DeFi, Not So Decentralized"). A dormant-member-heavy chat group is the same regime.
**What the rule does not do, and what covers it.** Deviating from simple majority needs justification: May's theorem characterizes simple majority as the unique rule satisfying anonymity, neutrality and positive responsiveness, and Rae's analysis (Rae 1969; see Berndt Rasmussen on the RaeTaylor theorem) grounds majority as the rule minimizing expected disappointment under symmetric preferences. Status-quo bias is defensible only when the costs of erroneous change exceed those of erroneous inaction, which is Polkadot's first justification and is plausible here: a wrongly replaced admin set is disruptive and the group cannot easily undo it mid-flight. But AQB has a property no voting-theoretic argument rescues: with zero nays, any non-empty aye set passes. Our protection is procedural, not arithmetic: the enforced referendum period and challenge window during which a single nay raises the bar steeply. That is why the RFC treats period enforcement as a security mechanism rather than a liveness convenience.
**Ballot secrecy: deliberately absent.** Votes are signed and third-party-verifiable within the group, so members can prove to each other how someone voted. The e-voting literature treats this as a serious defect: Juels, Catalano & Jakobsson, ["Coercion-Resistant Electronic Elections"](https://doi.org/10.1145/1102199.1102213) (WPES 2005) formalize why, and Helios (Adida, USENIX Security 2008) explicitly disclaims suitability for coercive settings. The risk here is concrete and small-scale: incumbent admins can identify nay voters and retaliate before the referendum concludes, and the RFC's own removal-deferral rule exists because they will try. We accept it because verifiability is what makes a certificate self-authenticating, which is the property that lets any member carry the result past a hostile forwarder. Coercion-resistant schemes need either a trusted tallier or heavyweight cryptography with a registration phase, neither of which fits. Note also the layering tension worth being explicit about: SimpleX's transport is deliberately deniable, and we are adding application-level non-repudiation on top of it for exactly one message class.
## 6. Sybil resistance: out of scope, and the literature says why that is hard
Douceur, "The Sybil Attack" (IPTPS 2002), shows that "without a logically centralized authority, Sybil attacks are always possible except under extreme and unrealistic assumptions of resource parity and coordination among entities." Our electorate is the admin-curated member list, so a group whose admins admitted sock puppets has a corrupted electorate before any vote occurs. This is stated as a limitation rather than solved.
Social-graph defences (Yu et al., SybilGuard, SIGCOMM 2006; SybilLimit, IEEE S&P 2008) exploit the sparse cut between honest and Sybil regions of a trust graph, and require a fast-mixing honest region. Alvisi, Clement, Epasto, Lattanzi & Panconesi's SoK (IEEE S&P 2013) and Viswanath et al. (SIGCOMM 2010) find these schemes essentially perform community detection and degrade when that structure is absent. A chat group's membership graph is small and is a co-membership graph, not a vetted trust graph, so these are a poor fit as-is. Kleppmann & Howard's Sybil framing is the more useful one for us: their BEC applications are Sybil-*immune* because they tolerate arbitrarily many Byzantine nodes, an option not open to us, since a majority vote is by definition a headcount and headcounts are what Sybils attack.
## 7. Accountability as a substitute for prevention
Our state attestations put members on record as having enacted a result, without proving honesty. The reference point is PeerReview (Haeberlen, Kouznetsov & Druschel, SOSP 2007), which achieves accountability via tamper-evident logs and witness sets, detecting any observable fault; and Haeberlen & Kuznetsov, "The Fault Detection Problem" (OPODIS 2009), which delimits what is detectable at all. Our mechanism is materially weaker (signed statements about applied state, not complete logs with witness coverage), so we should claim attribution, not detection completeness.
Accountable safety in the blockchain sense (Buterin & Griffith, ["Casper the Friendly Finality Gadget"](https://arxiv.org/abs/1710.09437), 2017) makes attribution meaningful by attaching slashable stake. There is no stake in a chat group; attribution is enforceable only socially, by the group's own subsequent referendum. ERA reaches the same conclusion for the same reason. This is why the RFC's stale-mandate defence is framed as making late enactment "slow, detectable, and attributable" rather than impossible; with no stake, that is the whole of what accountability can deliver.
Finally, the right vocabulary for what a system achieves when it cannot prevent equivocation is **fork consistency**: Mazières & Shasha, ["Building Secure File Systems out of Byzantine Storage"](https://doi.org/10.1145/571825.571840) (PODC 2002) and [SUNDR](https://www.usenix.org/conference/osdi-04/secure-untrusted-data-repository-sundr) (Li, Krohn, Mazières & Shasha, OSDI 2004); Cachin, Shelat & Shraer, ["Efficient Fork-Linearizable Access to Untrusted Shared Memory"](https://doi.org/10.1145/1281100.1281121) (PODC 2007); Li & Mazières, "Beyond One-Third Faulty Replicas in Byzantine Fault Tolerant Systems" (NSDI 2007). Fork-linearizability guarantees that once two clients' views diverge they can never again be made consistent, making the fork permanent and therefore detectable. **Our design is not fork-consistent, and deliberately so**: the same-version supersede rule and catch-up exist to *re-merge* diverged members. We trade the detectability guarantee for repair. That is a defensible choice for a chat group, where a permanently forked group is a worse outcome than a briefly inconsistent one, but the RFC should not borrow the language of fork consistency, because it provides something different.
## 8. Reconfiguration without consensus
Our version chain is a reconfiguration sequence, and there is a positive result here: Aguilera, Keidar, Malkhi & Shraer, "Dynamic Atomic Storage Without Consensus" (PODC 2009 / *JACM* 58(2), 2011) show that "perhaps surprisingly, dynamic R/W storage is solvable in a completely asynchronous system", contradicting the then-common belief that reconfiguration requires consensus. This supports the general shape of what we are doing. The caveat matters, though: DynaStore assumes crash faults, not Byzantine ones, and provides read/write storage rather than arbitrary policy evaluation. It shows reconfiguration is not *inherently* consensus-hard; it does not license the stronger claim that our Byzantine, policy-carrying variant inherits that.
## Summary of positions
We adopt: consensus-number analysis as the justification for revisable finality (Frey/Gestin/Raynal); I-confluent vote aggregation with a non-consensus decision rule (Kleppmann & Howard); majority-certificate authority in place of seniority ranking (motivated by Dougal's critique); epoch-style versioning without a serializer (MLS, minus its Delivery Service); flickering-tolerant membership (as DCGKA does); authenticated membership events (Rösler et al.); positive turnout bias (Burdges et al.), with procedural rather than arithmetic protection against the zero-nay case; and social-only accountability (PeerReview's framing, without Casper's stake).
We reject: anti-flickering revocation semantics (consensus number *N*); a finality arbiter (ERA) or delivery-service serializer (MLS), both of which reintroduce a trusted chokepoint; fixed participation quorums (abstention incentives, and DAO turnout empirics); ballot secrecy (incompatible with self-authenticating certificates); social-graph Sybil defence (wrong graph, wrong scale, for v1); and fork consistency as a goal (we repair forks rather than making them permanent).
Two places where the literature suggests the RFC is currently weaker than it needs to be: acceptance is not a pure function of received updates, which is a real deviation from strong eventual consistency and the root of knife-edge divergence (§2); and the temporal rules in the witnessed chain are backdating mitigations of the kind ERA classifies as insufficient without an arbiter, so their contribution should be described as raising cost rather than establishing a bound (§3).
+685 -128
View File
@@ -1,252 +1,809 @@
# Democratic groups: member referenda with adaptive quorum biasing
## Motivation
From a member's point of view, a SimpleX group belongs to whoever created it, permanently. That person (or whoever they
promoted, or whoever ends up holding their device) can rename the group, rewrite its rules, remove any member, silence
them for everyone, and delete the whole thing. Members have no recourse except leaving: rebuilding elsewhere, losing the
history, re-inviting everyone by hand and hoping they follow. In a handful of friends this may be fine. In a
small or mid-sized community (one that has outgrown its founding circle, developed its own norms and its own reason to
exist, and would carry on if any particular person left), it means the community is a guest in someone else's space, and
everyone can feel it the first time a disagreement with an admin has no procedure attached to it.
The failure modes are not hypothetical, and this project has already hit them: "we already had several accidental
deletions or lost owner accounts" (`2024-03-14-super-peers.md`). Today an owner who loses their device takes the group's
future with them; the spec notes that if the only owner leaves, the group can no longer be deleted, and nobody can
update its profile or fix its link. A compromised owner is worse, and an owner who simply becomes hostile is worst of
all, because the software is entirely on their side. What members want here is mundane: a way for the group to outlive
any one person's device, absence, or bad behaviour, without abandoning the group and starting over.
Nathan Schneider calls this default *implicit feudalism*, [*Governable Spaces: Democratic Design for Online
Life*](https://www.ucpress.edu/books/governable-spaces/paper) (University of California Press, 2024): "a bias, both
cultural and technical, for building communities as fiefdoms", in which platforms nudge users to tolerate nearly
all-powerful admins and benevolent dictators for life. His argument is that this is a design choice rather than a
technical necessity, and that it teaches its own politics: people whose everyday online spaces are never self-governing
stop expecting self-governance anywhere. A messenger that implements only the owner role can host only fiefdoms, however
decentralised its transport. This RFC does not make groups democratic by default; many groups should stay exactly as
they are. It makes democracy *available*: an opt-in, per-group mechanism so that a community which wants to hold its
admins accountable can do so inside the app, with voice rather than only exit.
## Problem
Group admin power is absolute and unaccountable. In p2p groups any admin can remove members and demote other admins, only owners can touch owners, and there is no recovery when owners are inactive, lost, or hostile (`docs/protocol/simplex-chat.md`: "If the only group `owner` leaves the group, it will not be possible to delete it"). Worse, admins *are* the group infrastructure: they are the only message forwarders for not-yet-connected member pairs (`isUserGrpFwdRelay`), the only legitimate introducers of new members, and the threat model explicitly grants them the ability to MITM introductions, selectively drop or modify forwarded messages, and "disrupt decentralized group state by sending different messages... to different group members" (`docs/protocol/simplex-chat.md`, threat model).
Group admin power is absolute and unaccountable. In p2p groups any admin can remove members and demote other admins,
only owners can touch owners, and there is no recovery when owners are inactive, lost, or hostile
(`docs/protocol/simplex-chat.md`: "If the only group `owner` leaves the group, it will not be possible to delete it").
Worse, admins *are* the group infrastructure: they are the only message forwarders for not-yet-connected member pairs
(`isUserGrpFwdRelay`), the only legitimate introducers of new members, and the threat model explicitly grants them the
ability to MITM introductions, selectively drop or modify forwarded messages, and "disrupt decentralized group state by
sending different messages... to different group members" (`docs/protocol/simplex-chat.md`, threat model).
Communities that want democratic self-governance have no mechanism for it. This was named as an aspiration in `2024-03-14-super-peers.md`: "create democratically governed communities when creators don't own the community... as the community grows it can elect the new admins or moderators from the existing members". `2023-05-02-groups.md` concluded that "some sort of consensus protocol is still needed for all membership changes other than member addition", and `2024-04-01-super-peers-2.md` drafted an approval vocabulary (`MemberApproval`, `GroupConsensus`) — with quorums drawn from admins and owners only (an affected ordinary member counter-signs their own promotion, but the general membership holds no vote).
Communities that want democratic self-governance have no mechanism for it. This was named as an aspiration in
`2024-03-14-super-peers.md`: "create democratically governed communities when creators don't own the community... as the
community grows it can elect the new admins or moderators from the existing members". `2023-05-02-groups.md` concluded
that "some sort of consensus protocol is still needed for all membership changes other than member addition", and
`2024-04-01-super-peers-2.md` drafted an approval vocabulary (`MemberApproval`, `GroupConsensus`), with quorums drawn
from admins and owners only (an affected ordinary member counter-signs their own promotion, but the general membership
holds no vote).
This RFC proposes an **opt-in** governance mode for p2p groups in which the members themselves can atomically replace the entire admin set via a referendum, with a turnout-adaptive majority rule, such that incumbent admins can neither forge, veto, nor block the process once it has started.
This RFC proposes an **opt-in** governance mode for p2p groups in which the members themselves can atomically replace
the entire admin set via a referendum, with a turnout-adaptive majority rule, such that incumbent admins can neither
forge, veto, nor block the process once it has started.
Design constraints inherited from the stack:
- Deniability is a hard design goal of the messaging layer there is deliberately no non-repudiation below the chat layer, so third-party-verifiable votes must be application-level signatures. This is the same path already taken for channel roster events (`requiresSignature` in `Protocol.hs`; design in `2025-04-14-signing-messages.md`).
- At the SMP layer, only a queue's own parties hold its keys: recipient commands (suspend, delete, key changes) require the recipient's key, and queue rotation is a two-party negotiation. No group member can act on another pair's queues, so admins have no handle on other members' direct connections. What *can* drop messages is a queue's hosting router — wholesale, "detectable only over other, redundant queues" (SMP threat model), which also forbids undetectably dropping individual messages — but each member chooses their own receiving routers, so vote censorship at that level requires collusion of the receiver's own chosen infrastructure, unrelated to admin power, and is mitigated by queue redundancy and rotation. Direct member connections are therefore the censorship-resistant substrate votes travel on.
- Interactive BFT consensus among mobile clients was assessed as impractical (`2023-10-20-group-integrity.md`: "progress seems unlikely or very slow"). The design below needs no interactive consensus — only asynchronous collection of signatures and a deterministic local tally rule.
- Deniability is a hard design goal of the messaging layer; there is deliberately no non-repudiation below the chat
layer, so third-party-verifiable votes must be application-level signatures. This is the same path already taken for
channel roster events (`requiresSignature` in `Protocol.hs`; design in `2025-04-14-signing-messages.md`).
- At the SMP layer, only a queue's own parties hold its keys: recipient commands (suspend, delete, key changes) require
the recipient's key, and queue rotation is a two-party negotiation. No group member can act on another pair's queues,
so admins have no handle on other members' direct connections. What *can* drop messages is a queue's hosting router:
wholesale, "detectable only over other, redundant queues" (SMP threat model), which also forbids undetectably dropping
individual messages, but each member chooses their own receiving routers, so vote censorship at that level requires
collusion of the receiver's own chosen infrastructure, unrelated to admin power, and is mitigated by queue redundancy
and rotation. Direct member connections are therefore the censorship-resistant substrate votes travel on.
- Interactive BFT consensus among mobile clients was assessed as impractical (`2023-10-20-group-integrity.md`: "progress
seems unlikely or very slow"). The design below needs no interactive consensus, only asynchronous collection of
signatures and a deterministic local tally rule.
## Solution
A group can opt in to **governed mode**. Enabling it converts all owners to admins a governed group has no owners, and governed clients reject any event that would create or promote a `GROwner` member, permanently. Governance parameters are fixed by the genesis certificate — they are not part of the admin-editable group profile and cannot be changed unilaterally.
A group can opt in to **governed mode**. Enabling it converts all owners to admins; a governed group has no owners, and
governed clients reject any event that would create or promote a `GROwner` member, permanently. Governance parameters
are fixed by the genesis certificate; they are not part of the admin-editable group profile and cannot be changed
unilaterally.
Any member may then start a **referendum** to replace the admin set. Members vote aye/nay with Ed25519 signatures over the proposal hash, sent to all members over their direct connections. Anyone can assemble the votes into a **certificate** — a self-authenticating proof that the proposal passed. Every member validates the certificate independently against its own record of members and keys and applies the new admin set atomically under a monotonic governance version, generalizing the version-gated atomic set replacement already implemented for channel rosters (`applyAtRosterVersion`).
Any member may then start a **referendum** to replace the admin set. Members vote aye/nay with Ed25519 signatures over
the proposal hash, sent to all members over their direct connections. Anyone can assemble the votes into a
**certificate**, a self-authenticating proof that the proposal passed. Every member validates the certificate
independently against its own record of members and keys and applies the new admin set atomically under a monotonic
governance version, generalizing the version-gated atomic set replacement already implemented for channel rosters
(`applyAtRosterVersion`).
The tally rule is **adaptive quorum biasing** (positive turnout bias), as used in Polkadot governance v1 (Burdges et al., "Overview of Polkadot and its Design Considerations", 2020): at low turnout a supermajority of votes cast is required; as turnout approaches the full electorate the threshold approaches simple majority. This avoids fixed quorums that dormant members would make unreachable. As in Polkadot, the protection against a small minority passing a referendum unopposed is not the curve alone but the *voting period* during which anyone can cast a nay — so receivers validate the period structurally, and every member is guaranteed an objection window between seeing a non-unconditional result and applying it (see "Timing"; a strict-majority result applies immediately, which is safe because no further vote can change it). A certificate whose ayes exceed half the electorate is immune to vote-withholding attacks and applies immediately; smaller-turnout certificates apply after a challenge window (see "Certificate soundness").
The tally rule is **adaptive quorum biasing** (positive turnout bias), as used in Polkadot governance v1 (Burdges et
al., "Overview of Polkadot and its Design Considerations", 2020): at low turnout a supermajority of votes cast is
required; as turnout approaches the full electorate the threshold approaches simple majority. This avoids fixed quorums
that dormant members would make unreachable. As in Polkadot, the protection against a small minority passing a
referendum unopposed is not the curve alone but the *voting period* during which anyone can cast a nay, so receivers
validate the period structurally, and every member is guaranteed an objection window between seeing a non-unconditional
result and applying it (see "Timing"; a strict-majority result applies immediately, which is safe because no further
vote can change it). A certificate whose ayes exceed half the electorate is immune to vote-withholding attacks and
applies immediately; smaller-turnout certificates apply after a challenge window (see "Certificate soundness").
Incumbents cannot block a referendum in progress because: governance events are self-authenticating and accepted from *any* member or forwarder (exempt from the single-`expectedForwarder` rule); voting rights are fixed at proposal time, so removing or demoting voters mid-referendum does not invalidate their votes; connection teardown and send-path lockout for removed members are deferred while a referendum is active; and certificates need no cooperation from any admin to be assembled, transported, or applied. What incumbents can still do *before* a referendum exists is inherited from the p2p group layer and stated under limitations.
Incumbents cannot block a referendum in progress because: governance events are self-authenticating and accepted from
*any* member or forwarder (exempt from the single-`expectedForwarder` rule); voting rights are fixed at proposal time,
so removing or demoting voters mid-referendum does not invalidate their votes; connection teardown and send-path lockout
for removed members are deferred while a referendum is active; and certificates need no cooperation from any admin to be
assembled, transported, or applied. What incumbents can still do *before* a referendum exists is inherited from the p2p
group layer and stated under limitations.
## Design
### Scope (v1)
- p2p groups only (`useRelays = false`). Relay groups/channels are future work (see below): they are single-owner today, and replacing their owner set requires changes to the short-link owner chain in simplexmq. That design is deliberately limited: `simplexmq/rfcs/2025-04-04-short-links-for-groups.md` states its purpose "is not to comprehensively manage ownership changes... but rather to ensure access continuity", ranks owners so that the creator cannot be removed, and leaves owner-change coordination to "some simple consensus protocol between owners" that does not yet exist.
- One referendum action: replace the set of `GRAdmin` members. Moderator and member roles are untouched (manageable by the new admins). The action type is a sum type (`GovAction`) so profile changes, governance-parameter changes, and group deletion can become referendum actions later without protocol redesign.
- Fixed positive turnout bias. Other curves (negative bias, simple majority of votes cast) are parameters left for later.
- p2p groups only (`useRelays = false`). Relay groups/channels are future work (see below): they are single-owner today,
and replacing their owner set requires changes to the short-link owner chain in simplexmq. That design is deliberately
limited: `simplexmq/rfcs/2025-04-04-short-links-for-groups.md` states its purpose "is not to comprehensively manage
ownership changes... but rather to ensure access continuity", ranks owners so that the creator cannot be removed, and
leaves owner-change coordination to "some simple consensus protocol between owners" that does not yet exist.
- One referendum action: replace the set of `GRAdmin` members. Moderator and member roles are untouched (manageable by
the new admins). The action type is a sum type (`GovAction`) so profile changes, governance-parameter changes, and
group deletion can become referendum actions later without protocol redesign.
- Fixed positive turnout bias. Other curves (negative bias, simple majority of votes cast) are parameters left for
later.
### Prerequisite: member signing keys in p2p groups
Channels already give every member a per-group Ed25519 key (`GroupMember.memberPubKey`, announced on join). The wire format already distributes keys in p2p groups: `MemberInfo` has a `memberKey :: Maybe MemberKey` field, populated unconditionally by `memberInfo` (`Library/Internal.hs`) from `memberPubKey`, and `MemberInfo` travels in `XGrpMemNew`, `XGrpMemIntro` and `XGrpMemFwd`. The verification path for signed messages in p2p groups (no `GroupKeys`/`publicGroupId`) also already exists: `withVerifiedMsg` verifies `CBGroup` signatures with prefix `smpEncode chatBinding <> smpEncode (memberId, pubKey)` ("forward compatibility for verifying signed messages in p2p groups", `Library/Subscriber.hs`).
Channels already give every member a per-group Ed25519 key (`GroupMember.memberPubKey`, announced on join). The wire
format already distributes keys in p2p groups: `MemberInfo` has a `memberKey :: Maybe MemberKey` field, populated
unconditionally by `memberInfo` (`Library/Internal.hs`) from `memberPubKey`, and `MemberInfo` travels in `XGrpMemNew`,
`XGrpMemIntro` and `XGrpMemFwd`. The verification path for signed messages in p2p groups (no `GroupKeys`/
`publicGroupId`) also already exists: `withVerifiedMsg` verifies `CBGroup` signatures with prefix
`smpEncode chatBinding <> smpEncode (memberId, pubKey)` ("forward compatibility for verifying signed messages in p2p
groups", `Library/Subscriber.hs`).
What is missing for p2p groups: generating a per-group member key pair, persisting the private key (a `member_priv_key` independent of the channels-only `GroupKeys` record), populating `memberPubKey` on join and introduction, and TOFU-pinning received keys (rejecting a *different* key for a known member, as `applyMemberKeyRole` does for channels). Members of existing groups announce their key on upgrade (extension of `XGrpMemInfo` or a dedicated announcement). Governance requires keys for all electorate members; enabling fails while any current member's key is unknown.
What is missing for p2p groups: generating a per-group member key pair, persisting the private key (a `member_priv_key`
independent of the channels-only `GroupKeys` record), populating `memberPubKey` on join and introduction, and
TOFU-pinning received keys (rejecting a *different* key for a known member, as `applyMemberKeyRole` does for channels).
Members of existing groups announce their key on upgrade (extension of `XGrpMemInfo` or a dedicated announcement).
Governance requires keys for all electorate members; enabling fails while any current member's key is unknown.
Per-group keys also provide domain separation: a vote signature cannot be replayed in another group because both `MemberId`s and keys are unique per group.
Per-group keys also provide domain separation: a vote signature cannot be replayed in another group because both
`MemberId`s and keys are unique per group.
### Enabling governance: the genesis certificate
Preconditions: every current member's negotiated chat version supports governance (members below the version floor must leave or be removed first — a client that silently ignores governance events would diverge), and every current member's key is known.
Preconditions: every current member's negotiated chat version supports governance (members below the version floor must
leave or be removed first; a client that silently ignores governance events would diverge), and every current member's
key is known.
An owner initiates enabling with chosen parameters. Enabling is itself a unanimity referendum among the current owners: the initiating owner collects signatures from all co-owners (trivial for sole-owner groups) and broadcasts the genesis certificate as `x.grp.gov.enable`:
An owner initiates enabling with chosen parameters. Enabling is itself a unanimity referendum among the current owners:
the initiating owner collects signatures from all co-owners (trivial for sole-owner groups) and broadcasts the genesis
certificate as `x.grp.gov.enable`:
- mint a random 256-bit `governanceId` a group-scoped identifier that exists only inside e2e-encrypted messages (see metadata note under limitations);
- mint a random 256-bit `governanceId`, a group-scoped identifier that exists only inside e2e-encrypted messages (see
metadata note under limitations);
- `params`: `{referendumDays (default 7), challengeHours (default 24), settleSkewDays (default 7)}`;
- the initial electorate (list and hash, as defined in the next section);
- signed bytes: `smpEncode ("SXGG", governanceId, params, electorateHash)`, signed by every current owner.
Receivers validate that the signer set is exactly the set of members they record as `GROwner`, that the parameters lie within protocol-defined bounds — `1 ≤ referendumDays ≤ 30`, `1 ≤ challengeHours ≤ 24·referendumDays` (an unbounded window would make every non-unconditional referendum unresolvable, silently reducing governance to strict-majority-of-electorate forever), `0 ≤ settleSkewDays ≤ referendumDays` — rejected fail-closed otherwise (every safety property below is a function of these values, and the enabling owner is exactly the actor governance exists to constrain; a zero period or a vacuously large settlement skew would void the objection guarantees), and validate the electorate (checks defined below, with the genesis message's broker timestamp standing in for `proposedAt`; fail-closed on conflict), then apply atomically: store governance state, demote all `GROwner` members (including the sender and possibly themselves) to `GRAdmin`, set governance version 1.
Receivers validate that the signer set is exactly the set of members they record as `GROwner`, that the parameters lie
within protocol-defined bounds: `1 ≤ referendumDays ≤ 30`, `1 ≤ challengeHours ≤ 24·referendumDays` (an unbounded
window would make every non-unconditional referendum unresolvable, silently reducing governance to
strict-majority-of-electorate forever), `0 ≤ settleSkewDays ≤ referendumDays`, rejected fail-closed otherwise (every
safety property below is a function of these values, and the enabling owner is exactly the actor governance exists to
constrain; a zero period or a vacuously large settlement skew would void the objection guarantees), and validate the
electorate (checks defined below, with the genesis message's broker timestamp standing in for `proposedAt`; fail-closed
on conflict), then apply atomically: store governance state, demote all `GROwner` members (including the sender and
possibly themselves) to `GRAdmin`, set governance version 1.
From this point the group is owner-free: no member holds `GROwner`, and governed clients additionally **reject any event that would create or set a member with role `GROwner`**`XGrpMemRole` to owner, and owner-role members arriving via `XGrpMemNew`, `XGrpMemIntro`, `XGrpMemFwd`, `XGrpLinkInv` or `XGrpInv`. The explicit rejection rule matters: the existing role gates alone do not close this (`xGrpMemIntro` accepts the introduced member's role verbatim from the host at any time, so a hostile host could otherwise mint a fake "owner" in a victim's local view and then feed it owner-gated events). With the rule in place, `XGrpDel` (receiver gate: sender must be `GROwner`) is dead in governed groups — nobody can remotely delete the group, which also closes the "delete the group to pre-empt the vote" attack. Owner-gated checks for `XGrpInfo` and `XGrpPrefs` are relaxed to `GRAdmin` in governed groups on both sides — the receiver gates and the sender-side assertion in `runUpdateGroupProfile` (otherwise an owner-free group could never edit its profile again); governance parameters are not carried in the profile and have no update path (v1: governance is a one-way door until a `GAChangeGovernance` action ships; see open questions).
From this point the group is owner-free: no member holds `GROwner`, and governed clients additionally **reject any event
that would create or set a member with role `GROwner`**: `XGrpMemRole` to owner, and owner-role members arriving via
`XGrpMemNew`, `XGrpMemIntro`, `XGrpMemFwd`, `XGrpLinkInv` or `XGrpInv`. The explicit rejection rule matters: the
existing role gates alone do not close this (`xGrpMemIntro` accepts the introduced member's role verbatim from the host
at any time, so a hostile host could otherwise mint a fake "owner" in a victim's local view and then feed it owner-gated
events). With the rule in place, `XGrpDel` (receiver gate: sender must be `GROwner`) is dead in governed groups; nobody
can remotely delete the group, which also closes the "delete the group to pre-empt the vote" attack. Owner-gated checks
for `XGrpInfo` and `XGrpPrefs` are relaxed to `GRAdmin` in governed groups on both sides: the receiver gates and the
sender-side assertion in `runUpdateGroupProfile` (otherwise an owner-free group could never edit its profile again);
governance parameters are not carried in the profile and have no update path (v1: governance is a one-way door until a
`GAChangeGovernance` action ships; see open questions).
For newly created groups the creator enables governance at creation (self-signed genesis, electorate of one). Joiners to a governed group receive the genesis certificate from their host together with the group profile; like everything else a joiner learns about a p2p group, it is trusted on the host's word (see limitations). To make a forged or divergent genesis *detectable*, clients must surface governance events carrying a `governanceId` different from their stored one, rather than silently dropping them — governance events are forwarded by everyone, so a victim of a fake genesis will see mismatching traffic.
For newly created groups the creator enables governance at creation (self-signed genesis, electorate of one). Joiners to
a governed group receive the genesis certificate from their host together with the group profile; like everything else a
joiner learns about a p2p group, it is trusted on the host's word (see limitations). To make a forged or divergent
genesis *detectable*, clients must surface governance events carrying a `governanceId` different from their stored one,
rather than silently dropping them; governance events are forwarded by everyone, so a victim of a fake genesis will see
mismatching traffic.
### Electorate
The electorate for a referendum is **all settled current members** status connected or beyond (`GSMemConnected`, `GSMemComplete`, `GSMemCreator`) — regardless of role and regardless of `blockedByAdmin`, excluding only `GRRelay` (not applicable to p2p groups, excluded defensively). Members in transient join states are not in the electorate. Basing eligibility on anything role- or restriction-shaped is deliberately avoided: those are levers incumbents control (demote dissidents to observer, block them "for all") and would hand them a disenfranchisement tool. The only way to keep someone out of a future electorate is to remove them from the group before a referendum starts — see limitations.
The electorate for a referendum is **all settled current members**, status connected or beyond (`GSMemConnected`,
`GSMemComplete`, `GSMemCreator`), regardless of role and regardless of `blockedByAdmin`, excluding only `GRRelay` (not
applicable to p2p groups, excluded defensively). Members in transient join states are not in the electorate. Basing
eligibility on anything role- or restriction-shaped is deliberately avoided: those are levers incumbents control (demote
dissidents to observer, block them "for all") and would hand them a disenfranchisement tool. The only way to keep
someone out of a future electorate is to remove them from the group before a referendum starts; see limitations.
"Settled" is a per-client observation (each client marks a member connected on its own handshake with them), so honest members' views of the electorate boundary can differ for recently joined members. The validation rules below use a settlement-skew tolerance for exactly this reason, and clients record *when* each member became settled.
"Settled" is a per-client observation (each client marks a member connected on its own handshake with them), so honest
members' views of the electorate boundary can differ for recently joined members. The validation rules below use a
settlement-skew tolerance for exactly this reason, and clients record *when* each member became settled.
The proposal ships the electorate as a list of `MemberId`s (chunked for large groups) plus `electorateHash` = hash of the sorted `(memberId, memberKey)` pairs. Each receiver validates it against its own database:
The proposal ships the electorate as a list of `MemberId`s (chunked for large groups) plus `electorateHash` = hash of
the sorted `(memberId, memberKey)` pairs. Each receiver validates it against its own database:
1. recompute `electorateHash` from the shipped `MemberId` list joined with the receiver's *own* recorded key for each member, sorted by `memberId`; the result must equal the proposal's `electorateHash` (which is inside the signed `proposalHash` — the list itself travels outside the signature and could otherwise be padded by a forwarder to inflate `E` at selected receivers; recomputing with the receiver's keys also binds the proposer's key view to the receiver's), and the proposer must appear in the list;
2. every listed member must be known to the receiver — an unknown member is ballot stuffing, reject (key divergence has no separate check: the list carries no keys, so it surfaces as a check-1 hash mismatch; this check exists to give a precise diagnostic);
3. every member the receiver records as current and settled earlier than `settleSkewDays` before `proposedAt` must be listed — an omission is disenfranchisement, reject. Members settled more recently may be listed or omitted (their settlement was plausibly still propagating when the proposal was made), and removals with broker timestamps close to `proposedAt` are likewise tolerated as omissions. Clients must surface to voters both the recently settled members a proposal omits and removals adjacent to a referendum — these tolerances are proposer discretion over the margins of the electorate, and the voters' remedy is a nay.
1. recompute `electorateHash` from the shipped `MemberId` list joined with the receiver's *own* recorded key for each
member, sorted by `memberId`; the result must equal the proposal's `electorateHash` (which is inside the signed
`proposalHash`; the list itself travels outside the signature and could otherwise be padded by a forwarder to
inflate `E` at selected receivers; recomputing with the receiver's keys also binds the proposer's key view to the
receiver's), and the proposer must appear in the list;
2. every listed member must be known to the receiver; an unknown member is ballot stuffing, reject (key divergence has
no separate check: the list carries no keys, so it surfaces as a check-1 hash mismatch; this check exists to give a
precise diagnostic);
3. every member the receiver records as current and settled earlier than `settleSkewDays` before `proposedAt` must be
listed; an omission is disenfranchisement, reject. Members settled more recently may be listed or omitted (their
settlement was plausibly still propagating when the proposal was made), and removals with broker timestamps close to
`proposedAt` are likewise tolerated as omissions. Clients must surface to voters both the recently settled members a
proposal omits and removals adjacent to a referendum; these tolerances are proposer discretion over the margins of
the electorate, and the voters' remedy is a nay.
Rejection is fail-closed: the member will not vote and will not apply the certificate. It is *recoverable* — see "Catch-up and recovery" below. With membership events additionally signed (see implementation), equivocating membership state to different members becomes evidence-producing: two contradictory signed membership statements from the same admin are third-party-verifiable proof of misbehavior.
Rejection is fail-closed: the member will not vote and will not apply the certificate. It is *recoverable*; see
"Catch-up and recovery" below. With membership events additionally signed (see implementation), equivocating membership
state to different members becomes evidence-producing: two contradictory signed membership statements from the same
admin are third-party-verifiable proof of misbehavior.
Members who join after the proposal are not in the electorate and cannot vote; they accept the referendum outcome on the same basis as everything else they learn about the group — from their introducing host. The governance guarantees hold for members present at proposal time.
Members who join after the proposal are not in the electorate and cannot vote; they accept the referendum outcome on the
same basis as everything else they learn about the group, from their introducing host. The governance guarantees hold
for members present at proposal time.
### Referendum protocol
New chat protocol events (JSON, version-gated): `x.grp.gov.enable` (above), and:
- `x.grp.gov.propose` `{governanceId, govVersion, action, electorate, electorateHash, prevProposalHash, proposedAt, expiresAt, proposer, sig}` — from any electorate member. `prevProposalHash` is the `proposalHash` of the referendum whose certificate the proposer applied at the previous version — the genesis certificate's hash at `govVersion = 2` — chaining each referendum to the state it was proposed against. It names the *proposal*, not the certificate, because certificates are not canonical: each member re-serves its own as-applied vote set (see "Catch-up and recovery"), so honest members at the same version would otherwise compute different chain values. `action = {type: "replaceAdmins", admins: [MemberId]}` with the proposed member IDs sorted. `proposer` is the proposer's `MemberId`, carried explicitly so that forwarded copies can be verified without trusting the forwarder's sender claim. `proposalHash` = SHA-256 of the deterministic binary encoding `smpEncode ("SXGP", governanceId, govVersion, action, electorateHash, prevProposalHash, proposedAt, expiresAt, proposer)`; `sig` is the proposer's signature over it. `govVersion` must be the receiver's stored governance version + 1 for full processing; a proposal claiming a higher version is not retained and only marks a possible version gap, triggering at most one rate-limited `x.grp.gov.request` with backoff (otherwise a single forged-version proposal would stampede the whole group into catch-up). Multiple proposals may coexist at the same version; clients retain up to one valid proposal per proposer per version (needed to validate competing certificates, bounded against floods by the electorate size) and members may vote on each independently — so flooding decoy proposals cannot lock anyone out of voting on the genuine one.
- `x.grp.gov.vote` `{governanceId, proposalHash, voter, vote, sig}``vote ∈ {aye, nay}`; `voter` is the voter's `MemberId` (for key lookup in forwarded copies; the binding is the signature itself); `sig` over `smpEncode ("SXGV", governanceId, proposalHash, vote)`. Sent by the voter to all members over direct connections (normal group fan-out). One vote per member per proposal; **conflicting signed votes from the same member on the same proposal annul that member's vote on it** (excluded from both tallies) — a deterministic rule under vote-set union, so equivocating voters cannot make different members tally differently.
- `x.grp.gov.cert` `{governanceId, proposalHash, votes}` — the certificate: the full vote list `[(memberId, vote, sig)]`, assembled and broadcast by any member after expiry. A certificate is validated against the retained proposal it references; a client that lacks the proposal requests it (below) before judging the certificate. An **announcement form** with `votes` omitted and `{certHash, tally}` present is used for the post-apply announcement (see "Applying a certificate"); peers that lack the full certificate request it. At ~100 bytes per vote, groups beyond ~120 members need the chunked-blob transport already used for roster blobs.
- `x.grp.gov.request` `{governanceId, haveVersion, proposalHash?}` — catch-up by version, or, with `proposalHash` present, a request for that specific proposal + certificate; see "Catch-up and recovery".
- `x.grp.gov.propose`
`{governanceId, govVersion, action, electorate, electorateHash, prevProposalHash, proposedAt, expiresAt, proposer, sig}`,
from any electorate member. `prevProposalHash` is the `proposalHash` of the referendum whose certificate the proposer
applied at the previous version (the genesis certificate's hash at `govVersion = 2`), chaining each referendum to the
state it was proposed against. It names the *proposal*, not the certificate, because certificates are not canonical:
each member re-serves its own as-applied vote set (see "Catch-up and recovery"), so honest members at the same version
would otherwise compute different chain values. `action = {type: "replaceAdmins", admins: [MemberId]}` with the
proposed member IDs sorted. `proposer` is the proposer's `MemberId`, carried explicitly so that forwarded copies can
be verified without trusting the forwarder's sender claim. `proposalHash` = SHA-256 of the deterministic binary
encoding
`smpEncode ("SXGP", governanceId, govVersion, action, electorateHash, prevProposalHash, proposedAt, expiresAt, proposer)`;
`sig` is the proposer's signature over it. `govVersion` must be the receiver's stored governance version + 1 for full
processing; a proposal claiming a higher version is not retained and only marks a possible version gap, triggering at
most one rate-limited `x.grp.gov.request` with backoff (otherwise a single forged-version proposal would stampede the
whole group into catch-up). Multiple proposals may coexist at the same version; clients retain up to one valid
proposal per proposer per version (needed to validate competing certificates, bounded against floods by the electorate
size) and members may vote on each independently, so flooding decoy proposals cannot lock anyone out of voting on the
genuine one.
- `x.grp.gov.vote` `{governanceId, proposalHash, voter, vote, sig}`: `vote ∈ {aye, nay}`; `voter` is the voter's
`MemberId` (for key lookup in forwarded copies; the binding is the signature itself); `sig` over
`smpEncode ("SXGV", governanceId, proposalHash, vote)`. Sent by the voter to all members over direct connections
(normal group fan-out). One vote per member per proposal; **conflicting signed votes from the same member on the same
proposal annul that member's vote on it** (excluded from both tallies), a deterministic rule under vote-set union, so
equivocating voters cannot make different members tally differently.
- `x.grp.gov.cert` `{governanceId, proposalHash, votes}`, the certificate: the full vote list
`[(memberId, vote, sig)]`, assembled and broadcast by any member after expiry. A certificate is validated against the
retained proposal it references; a client that lacks the proposal requests it (below) before judging the certificate.
An **announcement form** with `votes` omitted and `{certHash, tally}` present is used for the post-apply announcement
(see "Applying a certificate"); peers that lack the full certificate request it. At ~100 bytes per vote, groups
beyond ~120 members need the chunked-blob transport already used for roster blobs.
- `x.grp.gov.request` `{governanceId, haveVersion, proposalHash?}`, catch-up by version, or, with `proposalHash`
present, a request for that specific proposal + certificate; see "Catch-up and recovery".
Signatures are detached application-payload signatures over deterministic binary encodings, so they can be re-aggregated into certificates by third parties. This is a new pattern relative to shipped message signing: `2025-04-14-signing-messages.md` deliberately signs the transmitted bytes in an envelope to avoid re-encoding, which is the right choice for transport authentication but cannot support third-party re-aggregation of individual votes.
Signatures are detached application-payload signatures over deterministic binary encodings, so they can be re-aggregated
into certificates by third parties. This is a new pattern relative to shipped message signing:
`2025-04-14-signing-messages.md` deliberately signs the transmitted bytes in an envelope to avoid re-encoding, which is
the right choice for transport authentication but cannot support third-party re-aggregation of individual votes.
Transport rules (the anti-censorship core):
1. All `x.grp.gov.*` event types are added to `isForwardedGroupMsg` and are **exempt from `expectedForwarder`** and from the admin-only forwarder check in `xGrpMsgForward`: they are self-authenticating, so any current member may forward or rebroadcast them; the existing `sharedMsgId` dedup applies. An admin dropping them achieves nothing while any other path exists.
1. All `x.grp.gov.*` event types are added to `isForwardedGroupMsg` and are **exempt from `expectedForwarder`** and from
the admin-only forwarder check in `xGrpMsgForward`: they are self-authenticating, so any current member may forward
or rebroadcast them; the existing `sharedMsgId` dedup applies. An admin dropping them achieves nothing while any
other path exists.
2. Governance events are exempt from the `blockedByAdmin` forwarding suppression.
3. **Voting rights are fixed at proposal time.** A vote verifies against the electorate snapshot; removal, demotion, or blocking of the voter after the proposal does not invalidate it. Receivers keep removed-member records, so verification remains possible.
4. **Removal deferral**, in two tiers. (i) While a client holds an unresolved proposal, it defers the connection deletion normally triggered by `XGrpMemDel` — both for itself when removed and toward removed third parties — for members of that proposal's electorate (removals of non-electorate members proceed normally), until no live challenge window for the proposal can remain open: `expiresAt` + the certificate freshness horizon + the maximum window (terms defined under "Certificate soundness" and "Applying a certificate"). (ii) A client that has only seen governance traffic (votes, a certificate, an announcement) *referencing* a proposal it does not hold — a member behind on versions must still be protected — cannot know the electorate, so it defers **all** removals, but under bounds that must satisfy two adversarial requirements at once: chained references must not be able to suspend removal enforcement indefinitely, and the budget must not be pre-consumable by an attacker so as to schedule a purge into a predictably unprotected gap. Recommended shape: a reference stops counting if the proposal cannot be obtained within a bounded fetch timeout (suggested: 24h); references are budgeted *per originating direct peer* — one peer's spam cannot consume protection triggered by another's traffic — with forwarded and unsigned references sharing one small separate budget, each capped per rolling window. Exact budgets are implementation-defined DoS tuning (open question 6). In both tiers the client exempts `x.grp.gov.*` events from the removed-member send guard (the `memberRemoved` check that otherwise blocks all sending), so a removed member can keep voting on deferred connections. Deferred connections carry only `x.grp.gov.*` events. This neutralizes the strongest incumbent counter-attack: mass-removing opposition voters to silence them mid-vote (today, receiving `XGrpMemDel` about oneself both flips the member's own status to removed — locking the send path — and, for non-admin members, tears down all group connections immediately).
3. **Voting rights are fixed at proposal time.** A vote verifies against the electorate snapshot; removal, demotion, or
blocking of the voter after the proposal does not invalidate it. Receivers keep removed-member records, so
verification remains possible.
4. **Removal deferral**, in two tiers. (i) While a client holds an unresolved proposal, it defers the connection
deletion normally triggered by `XGrpMemDel` (both for itself when removed and toward removed third parties) for
members of that proposal's electorate (removals of non-electorate members proceed normally), until no live challenge
window for the proposal can remain open: `expiresAt` + the certificate freshness horizon + the maximum window (terms
defined under "Certificate soundness" and "Applying a certificate"). (ii) A client that has only seen governance
traffic (votes, a certificate, an announcement) *referencing* a proposal it does not hold (a member behind on
versions must still be protected) cannot know the electorate, so it defers **all** removals, but under bounds that
must satisfy two adversarial requirements at once: chained references must not be able to suspend removal enforcement
indefinitely, and the budget must not be pre-consumable by an attacker so as to schedule a purge into a predictably
unprotected gap. Recommended shape: a reference stops counting if the proposal cannot be obtained within a bounded
fetch timeout (suggested: 24h); references are budgeted *per originating direct peer* (one peer's spam cannot
consume protection triggered by another's traffic), with forwarded and unsigned references sharing one small separate
budget, each capped per rolling window. Exact budgets are implementation-defined DoS tuning (open question 6). In
both tiers the client exempts `x.grp.gov.*` events from the removed-member send guard (the `memberRemoved` check that
otherwise blocks all sending), so a removed member can keep voting on deferred connections. Deferred connections
carry only `x.grp.gov.*` events. This neutralizes the strongest incumbent counter-attack: mass-removing opposition
voters to silence them mid-vote (today, receiving `XGrpMemDel` about oneself both flips the member's own status to
removed, locking the send path, and, for non-admin members, tears down all group connections immediately).
### Tally: adaptive quorum biasing
Let `E` = electorate size, `A` = valid aye votes, `B` = valid nay votes, `T = A + B` (turnout). The proposal passes iff (positive turnout bias, integer-exact form):
Let `E` = electorate size, `A` = valid aye votes, `B` = valid nay votes, `T = A + B` (turnout). The proposal passes iff
(positive turnout bias, integer-exact form):
```
B² · E < A² · T
```
equivalently `B/√T < A/√E`. Properties: at full turnout (`T = E`) this reduces to `A > B` (simple majority); at low turnout a supermajority of votes cast is required. Example, `E = 100`: at `T = 25`, passing requires `A ≥ 17` (68% of votes cast); at `T = 100`, `A ≥ 51`. Note that with zero nays *any* non-empty aye set passes — this is inherent to positive turnout bias (Polkadot's included). The defense for the silent majority is not the curve but the voting period and challenge window, during which nays are cheap and effective: at `E = 100`, a single nay forces `A ≥ 5`, five nays force `A ≥ 13`, twenty force `A ≥ 29` — resistance scales roughly as `A ≈ ∛(B²·E)`.
equivalently `B/√T < A/√E`. Properties: at full turnout (`T = E`) this reduces to `A > B` (simple majority); at low
turnout a supermajority of votes cast is required. Example, `E = 100`: at `T = 25`, passing requires `A ≥ 17` (68% of
votes cast); at `T = 100`, `A ≥ 51`. Note that with zero nays *any* non-empty aye set passes; this is inherent to
positive turnout bias (Polkadot's included). The defense for the silent majority is not the curve but the voting period
and challenge window, during which nays are cheap and effective: at `E = 100`, a single nay forces `A ≥ 5`, five nays
force `A ≥ 13`, twenty force `A ≥ 29`; resistance scales roughly as `A ≈ ∛(B²·E)`.
### Timing
There is no shared clock, so timing is enforced structurally where possible and with tolerances elsewhere:
- **Structural check (always):** `expiresAt proposedAt = referendumDays` exactly. Both fields are inside `proposalHash` and signed, so this is verifiable by anyone at any time, including during catch-up.
- **Plausibility check (directly received proposals only):** `proposedAt` must be within a skew allowance (suggested: 24h) of the message's broker timestamp. Broker timestamps are set when the message reaches the receiving queue, not when the client reads it, so this check is robust for offline receivers. Forwarded copies are not timing-checked — a forwarder's claimed timestamp proves nothing, and late delivery is indistinguishable from backdating; receivers of forwarded copies are protected by the challenge window instead.
- Voters vote while their local clock is before `expiresAt`, with one addition: a member that *first receives* a proposal at or after `expiresAt` (via forwards — a withheld or delayed proposal — or via catch-up delivery) may still cast its vote until its own challenge window closes, and broadcasts it normally. Such late votes are counted by every member whose window is still open and ignored by members already at local finality — which is exactly what turns a withheld-proposal attempt into a visible contested result instead of silent capture (see limitations). Receivers do not otherwise verify vote timing: a "late" vote is still that member's genuine vote, and expiry exists for liveness, not security — after local finality (below), late votes on that proposal are ignored.
- Receivers do not evaluate certificates before their local `expiresAt`, with one exception: an **unconditional** certificate (defined next) may be applied immediately, since no further vote can change its outcome.
- **Structural check (always):** `expiresAt proposedAt = referendumDays` exactly. Both fields are inside
`proposalHash` and signed, so this is verifiable by anyone at any time, including during catch-up.
- **Plausibility check (directly received proposals only):** `proposedAt` must be within a skew allowance (suggested:
24h) of the message's broker timestamp. Broker timestamps are set when the message reaches the receiving queue, not
when the client reads it, so this check is robust for offline receivers. Forwarded copies are not timing-checked: a
forwarder's claimed timestamp proves nothing, and late delivery is indistinguishable from backdating; receivers of
forwarded copies are protected by the challenge window instead.
- Voters vote while their local clock is before `expiresAt`, with one addition: a member that *first receives* a
proposal at or after `expiresAt` (via forwards of a withheld or delayed proposal, or via catch-up delivery) may still
cast its vote until its own challenge window closes, and broadcasts it normally. Such late votes are counted by every
member whose window is still open and ignored by members already at local finality, which is exactly what turns a
withheld-proposal attempt into a visible contested result instead of silent capture (see limitations). Receivers do
not otherwise verify vote timing: a "late" vote is still that member's genuine vote, and expiry exists for liveness,
not security; after local finality (below), late votes on that proposal are ignored.
- Receivers do not evaluate certificates before their local `expiresAt`, with one exception: an **unconditional**
certificate (defined next) may be applied immediately, since no further vote can change its outcome.
The invariant these rules produce: every member gets at least `challengeHours` between seeing a non-unconditional result and applying it, no matter how the proposal reached them; and a proposal cannot pass "quietly fast" among members who received it honestly, because directly distributed proposals carry a verifiable period.
The invariant these rules produce: every member gets at least `challengeHours` between seeing a non-unconditional result
and applying it, no matter how the proposal reached them; and a proposal cannot pass "quietly fast" among members who
received it honestly, because directly distributed proposals carry a verifiable period.
### Certificate soundness: unconditional certificates and the challenge window
Unlike a plain majority-of-electorate threshold (where omitting votes can only hurt the proposer), AQB counts nays against the proposal, so a malicious certificate assembler would omit nay votes. Certificate validation must be robust to selective inclusion:
Unlike a plain majority-of-electorate threshold (where omitting votes can only hurt the proposer), AQB counts nays
against the proposal, so a malicious certificate assembler would omit nay votes. Certificate validation must be robust
to selective inclusion:
- A certificate is **unconditional** if it would pass even with every electorate member not in the certificate counted as nay. Substituting `B ← B + (E T)`, `T ← E` in the condition reduces it to exactly `2A > E` — ayes are a strict majority of the whole electorate. No withheld or future vote can flip such a certificate (only annulment of an equivocated aye already inside it can — see limitations), and it is applied immediately.
- Any other valid certificate starts a local **challenge window** (`challengeHours`) beginning at `max(certificate receipt, local expiresAt)`. The receiving member rebroadcasts the certificate, any member holding valid votes absent from it (in particular, nay voters themselves) resends them, and members who first saw the proposal late may cast *new* votes under the late-voting rule above; `x.grp.gov.vote` is idempotent, so no new event is needed. The window close is extension-aware and MUST be computed uniformly: the window closes `challengeHours` after the most recent previously-unseen valid nay for the proposal (or after the window start, if none arrived), capped at one `referendumDays` beyond the initial close (the **maximum window**`challengeHours + referendumDays` — is thus the longest a window can stay open) — this lets objection cohorts that learn of a result at different times aggregate instead of being evaluated piecemeal, and a drip-feed of nays cannot stall resolution past the cap. At window expiry the member evaluates the condition over the **union** of certificate votes and locally held votes, and applies iff it passes. Nay voters broadcast to the whole group over direct connections during the referendum period — much longer than the window — so in the honest-connectivity case every member already holds the nays an assembler might omit.
- A certificate is **unconditional** if it would pass even with every electorate member not in the certificate counted
as nay. Substituting `B ← B + (E T)`, `T ← E` in the condition reduces it to exactly `2A > E`: ayes are a strict
majority of the whole electorate. No withheld or future vote can flip such a certificate (only annulment of an
equivocated aye already inside it can; see limitations), and it is applied immediately.
- Any other valid certificate starts a local **challenge window** (`challengeHours`) beginning at
`max(certificate receipt, local expiresAt)`. The receiving member rebroadcasts the certificate, any member holding
valid votes absent from it (in particular, nay voters themselves) resends them, and members who first saw the proposal
late may cast *new* votes under the late-voting rule above; `x.grp.gov.vote` is idempotent, so no new event is needed.
The window close is extension-aware and MUST be computed uniformly: the window closes `challengeHours` after the most
recent previously-unseen valid nay for the proposal (or after the window start, if none arrived), capped at one
`referendumDays` beyond the initial close (the **maximum window**, `challengeHours + referendumDays`, is thus the
longest a window can stay open); this lets objection cohorts that learn of a result at different times aggregate
instead of being evaluated piecemeal, and a drip-feed of nays cannot stall resolution past the cap. At window expiry
the member evaluates the condition over the **union** of certificate votes and locally held votes, and applies iff it
passes. Nay voters broadcast to the whole group over direct connections during the referendum period (much longer
than the window), so in the honest-connectivity case every member already holds the nays an assembler might omit.
After a member has applied or finally rejected a certificate, further votes for that proposal are ignored (local finality), with one exception: votes arriving inside a catch-up bundle are evaluated as part of judging that bundle (see "Catch-up and recovery"). Members whose challenge windows saw different vote sets can diverge on a knife-edge tally; see "Catch-up and recovery" for what is and is not repairable.
After a member has applied or finally rejected a certificate, further votes for that proposal are ignored (local
finality), with one exception: votes arriving inside a catch-up bundle are evaluated as part of judging that bundle (see
"Catch-up and recovery"). Members whose challenge windows saw different vote sets can diverge on a knife-edge tally; see
"Catch-up and recovery" for what is and is not repairable.
### Applying a certificate
Application is a single local transaction, generalizing `applyAtRosterVersion`:
1. check `govVersion` **greater than** the stored governance version, and for a gap greater than one that the **witnessed chain** conditions below hold. A stale version is ignored as replay, with one exception: a valid same-version certificate for a *different* proposal supersedes the applied one iff it ranks higher in **mandate order** — more ayes first, then unconditional before non-unconditional, then smaller `proposalHash` as the final deterministic tie-break. Aye count leads because unconditionality is relative to each proposal's own electorate — a banked certificate from a small past electorate must not outrank a better-supported live one. All three ranking components are computed from the canonical certificate bytes and the referenced proposal's electorate, independent of locally held votes: local annulment governs whether a certificate is *acceptable*, never how it *ranks*, so the order is total and identical at every member;
1. check `govVersion` **greater than** the stored governance version, and (for a gap greater than one) that the
**witnessed chain** conditions below hold. A stale version is ignored as replay, with one exception: a valid
same-version certificate for a *different* proposal supersedes the applied one iff it ranks higher in **mandate
order**: more ayes first, then unconditional before non-unconditional, then smaller `proposalHash` as the final
deterministic tie-break. Aye count leads because unconditionality is relative to each proposal's own electorate: a
banked certificate from a small past electorate must not outrank a better-supported live one. All three ranking
components are computed from the canonical certificate bytes and the referenced proposal's electorate, independent of
locally held votes: local annulment governs whether a certificate is *acceptable*, never how it *ranks*, so the order
is total and identical at every member;
2. set every member listed in the action to `GRAdmin`; demote every other current `GRAdmin` to `GRMember`;
3. store the new governance version, winning proposal and certificate;
4. **announce the applied certificate once to all connections** — unconditional certificates included — as a compact announcement (`proposalHash`, certificate hash, tally). The announcement is a non-authoritative hint: its tally is display-only, and no client acts on it without fetching and validating the full certificate; peers fetch at most once per unseen certificate hash, bounding traffic to O(N) full certificate transfers instead of O(N²). This is what makes the same-version tiebreak converge: without it, two halves of a group that applied different certificates would each consider themselves current and never compare (a same-version certificate yields no version gap, so no catch-up trigger fires).
4. **announce the applied certificate once to all connections** (unconditional certificates included) as a compact
announcement (`proposalHash`, certificate hash, tally). The announcement is a non-authoritative hint: its tally is
display-only, and no client acts on it without fetching and validating the full certificate; peers fetch at most once
per unseen certificate hash, bounding traffic to O (N) full certificate transfers instead of O (N²). This is what
makes the same-version tiebreak converge: without it, two halves of a group that applied different certificates would
each consider themselves current and never compare (a same-version certificate yields no version gap, so no catch-up
trigger fires).
**The witnessed chain.** Version skipping exists so that a member which could not validate some version is not stuck forever — but an unconstrained skip would let colluders bank a reserve certificate at a version no honest referendum will ever reach, since honest proposals advance one step at a time. For a gap greater than one, a client therefore requires:
**The witnessed chain.** Version skipping exists so that a member which could not validate some version is not stuck
forever; but an unconstrained skip would let colluders bank a reserve certificate at a version no honest referendum
will ever reach, since honest proposals advance one step at a time. For a gap greater than one, a client therefore
requires:
- a bundle for every version between its stored version and the target, each certificate passing the tally **evaluated on the bundle's own vote set as served, without union** (like ranking, and unlike adoption, witnessing ignores locally held votes — otherwise a member holding extra nays could not witness a version others applied, and the knife-edge rejecter's escape hatch would close);
- chain integrity: each proposal's `prevProposalHash` matching *any* valid proposal the client holds at the preceding version — not solely the one that won there. Accepting any same-version sibling matters because a legitimate `N+1` proposal authored during a contested window names whichever `N` proposal its author had applied, and rejecting it would strand exactly the fail-closed or knife-edge member the escape hatch exists for; the check's purpose, proving the author knew the preceding referendum, is unaffected. The check governs witnessed-chain links only — never gap-1 processing or voting, where a receiver ignores `prevProposalHash` entirely. Both rules exist to keep a tolerated same-version race (see mandate order below) from turning a transient contested result into deadlock. Note also that the value is self-asserted and public — announcements broadcast the referendum's identity — so it proves only that the author knew the preceding referendum, not that they applied it;
- temporal consistency: each proposal's `proposedAt` after the preceding version's `expiresAt` — at version 1 the anchor is the later of the genesis certificate's broker timestamp (already recorded as its `proposedAt` stand-in) and the client's own record of applying it, since genesis is not a referendum and has no expiry — and the target's `expiresAt` in the past, since no referendum can have concluded in the future;
- a **frontier bound**: the target version may exceed by at most one the highest version attested (`SXGS`) by *several distinct* eligible members — outside the target certificate's aye set, and attesting through a path not controlled by the presenter (recommended: three, or every reachable eligible member if fewer). A single attester is not enough — one abstaining confederate is outside the aye set and can attest anything, the same weakness the stale-mandate limitation already concedes for corroboration — so the threshold is what converts fabrication from a one-member trick into a conspiracy of that size.
- a bundle for every version between its stored version and the target, each certificate passing the tally **evaluated
on the bundle's own vote set as served, without union** (like ranking, and unlike adoption, witnessing ignores locally
held votes; otherwise a member holding extra nays could not witness a version others applied, and the knife-edge
rejecter's escape hatch would close);
- chain integrity: each proposal's `prevProposalHash` matching *any* valid proposal the client holds at the preceding
version, not solely the one that won there. Accepting any same-version sibling matters because a legitimate `N+1`
proposal authored during a contested window names whichever `N` proposal its author had applied, and rejecting it
would strand exactly the fail-closed or knife-edge member the escape hatch exists for; the check's purpose, proving
the author knew the preceding referendum, is unaffected. The check governs witnessed-chain links only, never gap-1
processing or voting, where a receiver ignores `prevProposalHash` entirely. Both rules exist to keep a tolerated
same-version race (see mandate order below) from turning a transient contested result into deadlock. Note also that
the value is self-asserted and public (announcements broadcast the referendum's identity), so it proves only that the
author knew the preceding referendum, not that they applied it;
- temporal consistency: each proposal's `proposedAt` after the preceding version's `expiresAt`; at version 1 the anchor
is the later of the genesis certificate's broker timestamp (already recorded as its `proposedAt` stand-in) and the
client's own record of applying it, since genesis is not a referendum and has no expiry; and the target's `expiresAt`
in the past, since no referendum can have concluded in the future;
- a **frontier bound**: the target version may exceed by at most one the highest version attested (`SXGS`) by *several
distinct* eligible members, outside the target certificate's aye set, and attesting through a path not controlled by
the presenter (recommended: three, or every reachable eligible member if fewer). A single attester is not enough (one
abstaining confederate is outside the aye set and can attest anything, the same weakness the stale-mandate limitation
already concedes for corroboration), so the threshold is what converts fabrication from a one-member trick into a
conspiracy of that size.
Conditions 1 and 2 are gap-only; the temporal link and the frontier bound apply to *every* stale adoption, gap 1 included (see "Catch-up and recovery"). The first three are cheap independent checks but are not individually load-bearing: fabricating intermediate bundles costs only signatures — with zero nays any non-empty aye set passes the tally (see "Tally"), and witnesses are not electorate-validated — while the timestamps in a fabricated chain are attacker-chosen and unchecked on the catch-up path (the plausibility test of "Timing" applies only to directly received proposals). The temporal rule bounds a leap by the time since the *group's* last completed referendum, not since the member's own absence, so in a group that rarely holds referenda it permits a long chain; it is a cost-raiser, not the guarantee.
Conditions 1 and 2 are gap-only; the temporal link and the frontier bound apply to *every* stale adoption, gap 1
included (see "Catch-up and recovery"). The first three are cheap independent checks but are not individually
load-bearing: fabricating intermediate bundles costs only signatures (with zero nays any non-empty aye set passes the
tally (see "Tally"), and witnesses are not electorate-validated), while the timestamps in a fabricated chain are
attacker-chosen and unchecked on the catch-up path (the plausibility test of "Timing" applies only to directly received
proposals). The temporal rule bounds a leap by the time since the *group's* last completed referendum, not since the
member's own absence, so in a group that rarely holds referenda it permits a long chain; it is a cost-raiser, not the
guarantee.
What binds is the frontier bound, which anchors acceptance to state independently observed by several members with no stake in the target certificate. Genuine long-absence catch-up is unaffected — honest peers attest the real frontier — and a member that skipped one unvalidatable version still sees peers attesting it. The residual risk is a member whose only reachable peers are the conspiracy: it can be shown any frontier. That is the pre-existing partition and Sybil exposure (see limitations) rather than a new one, and it is the same reason an absolute cap on the accepted gap is worth considering (open question 6) — it bounds both this residual and the O(gap) fetch-and-store amplification a presenter can compel.
What binds is the frontier bound, which anchors acceptance to state independently observed by several members with no
stake in the target certificate. Genuine long-absence catch-up is unaffected (honest peers attest the real frontier),
and a member that skipped one unvalidatable version still sees peers attesting it. The residual risk is a member whose
only reachable peers are the conspiracy: it can be shown any frontier. That is the pre-existing partition and Sybil
exposure (see limitations) rather than a new one, and it is the same reason an absolute cap on the accepted gap is worth
considering (open question 6); it bounds both this residual and the O (gap) fetch-and-store amplification a presenter
can compel.
Mandate order makes same-version arbitration substantive: a certificate can displace only one with a weaker showing, and ayes cannot be ground — they are real member signatures — so the attacker-influenced `proposalHash` decides only ties between certificates of identical mandate strength. It still cannot elevate an unpassed proposal. Known race: actions taken by admins of the losing certificate during the overlap are invalid in the winners' view; clients should surface a "contested result" state, and new admins should avoid destructive actions until their certificate's window (including extensions) has closed unchallenged.
Mandate order makes same-version arbitration substantive: a certificate can displace only one with a weaker showing, and
ayes cannot be ground (they are real member signatures), so the attacker-influenced `proposalHash` decides only ties
between certificates of identical mandate strength. It still cannot elevate an unpassed proposal. Known race: actions
taken by admins of the losing certificate during the overlap are invalid in the winners' view; clients should surface a
"contested result" state, and new admins should avoid destructive actions until their certificate's window (including
extensions) has closed unchallenged.
**Certificate freshness.** The **freshness horizon** is a duration of one `referendumDays` following a proposal's `expiresAt`. A certificate is fresh if it reached the receiver within the horizon — anchored like proposal timing (see "Timing"): broker timestamp for directly received certificates (robust for offline readers), local first sight for forwarded copies. A fresh certificate is processed as above. A stale one is only a catch-up hint: it may be adopted solely through the corroborated catch-up path (see "Catch-up and recovery"). The same-version supersede exception in step 1 follows the same rule — live for a fresh competitor, corroborated-catch-up-only for a stale one — which keeps tiebreak convergence alive for members that were offline or partitioned during the race. The hard bound on withheld certificates, however, is not freshness but step 1's **version monotonicity plus the witnessed-chain rule**: a reserve cannot be parked above the group's reachable frontier — the witnessed chain binds acceptance to the frontier attested by several members outside the certificate's aye set, and the `prevProposalHash` chain means any completed honest referendum invalidates every chain pre-manufactured above it, since its identity could not have been known in advance — so a reserve dies once the group completes any referendum at a higher version, and a reserve at the *same* version can contest at most that one round — under mandate order it prevails only with a categorically stronger showing. Freshness and corroboration make late enactment slow, loud, and attributable rather than impossible (see the stale-mandate limitation).
**Certificate freshness.** The **freshness horizon** is a duration of one `referendumDays` following a proposal's
`expiresAt`. A certificate is fresh if it reached the receiver within the horizon, anchored like proposal timing (see
"Timing"): broker timestamp for directly received certificates (robust for offline readers), local first sight for
forwarded copies. A fresh certificate is processed as above. A stale one is only a catch-up hint: it may be adopted
solely through the corroborated catch-up path (see "Catch-up and recovery"). The same-version supersede exception in
step 1 follows the same rule (live for a fresh competitor, corroborated-catch-up-only for a stale one), which keeps
tiebreak convergence alive for members that were offline or partitioned during the race. The hard bound on withheld
certificates, however, is not freshness but step 1's **version monotonicity plus the witnessed-chain rule**: a reserve
cannot be parked above the group's reachable frontier: the witnessed chain binds acceptance to the frontier attested by
several members outside the certificate's aye set, and the `prevProposalHash` chain means any completed honest
referendum invalidates every chain pre-manufactured above it, since its identity could not have been known in advance,
so a reserve dies once the group completes any referendum at a higher version, and a reserve at the *same* version can
contest at most that one round; under mandate order it prevails only with a categorically stronger showing. Freshness
and corroboration make late enactment slow, loud, and attributable rather than impossible (see the stale-mandate
limitation).
Day-to-day admin powers are otherwise unchanged in governed groups: admins add/remove members, appoint moderators, and may even promote additional admins between referenda — packing the admin set is pointless when a referendum can replace the whole set at any time.
Day-to-day admin powers are otherwise unchanged in governed groups: admins add/remove members, appoint moderators, and
may even promote additional admins between referenda; packing the admin set is pointless when a referendum can replace
the whole set at any time.
### Catch-up and recovery
A member can be behind the group's governance version: it fail-closed on an electorate conflict, finally rejected a knife-edge certificate others applied, or was offline. Recovery (compare `XGrpRosterRequest` gap repair in the roster machinery this design generalizes):
A member can be behind the group's governance version: it fail-closed on an electorate conflict, finally rejected a
knife-edge certificate others applied, or was offline. Recovery (compare `XGrpRosterRequest` gap repair in the roster
machinery this design generalizes):
- A member that observes governance traffic referencing a version above its own sends `x.grp.gov.request {governanceId, haveVersion}`. The version is directly readable from a proposal and from signed membership events (in governed groups, signed `XGrpMemRole`/`XGrpMemDel` populate the existing `rosterVersion` field with the sender's governance version, and `XGrpMemNew` gains an equivalent optional field); a certificate or announcement carries no version, so for those the client first fetches the referenced proposal (bounded as in transport rule 4) and compares. All catch-up requests — whatever triggered them — share one per-group rate limit with backoff, and clients cap concurrent outstanding fetches per peer. The budget is partitioned by an observable criterion: fetches for references that arrived over direct member connections have reserved capacity that forwarded or unsigned references (announcements, votes by reference) cannot consume, with per-peer sub-budgets inside the reserve — admins are themselves direct peers of most members, so a single hostile direct peer must not be able to drain it — so reference spam cannot starve the objection path.
- Any member re-serves, per applied version above `haveVersion`, a bundle of the proposal and its **as-applied vote set** (the union it evaluated at finality, a superset of the broadcast certificate). To bound reflected amplification, a client serves a given requester only versions above what it last served them (as the roster machinery does with `roster_served_version`), and rate-limits serving per requester over time. The response also includes the *hashes* of any active proposals at the requester's new current version + 1 (full proposals are fetched on demand by `proposalHash`, keeping responses bounded — retention allows up to one proposal per proposer, each shipping an electorate list), so that a member advancing via catch-up regains the ability to vote in the live referendum instead of being silently disenfranchised (it still counts in `E`, so its forced abstention would raise the bar for everyone).
- The catching-up member validates each bundle: the structural timing check, signatures, and electorate checks evaluated against its recorded membership history at the bundle's `proposedAt` (settlement and removal times are recorded), skipping the live plausibility check. The tally is evaluated over the bundle's votes **union** its own locally held votes for that proposal, without a challenge window — the member is judging evidence of an outcome already final elsewhere, and *that* premise must be established, not assumed: before adopting a bundle (or a stale certificate routed here) that would advance or supersede its state, the client MUST apply the two load-bearing witnessed-chain conditions — the **temporal link** (the target's `proposedAt` after the `expiresAt` of the referendum it applied at its stored version, and the target's `expiresAt` in the past — both checkable from its own records) and the **frontier bound** (several distinct eligible attesters, as defined there) — *regardless of gap size*. A single-version step is not exempt: without this, a two-member conspiracy could walk a victim forward one version at a time through this window-free path, indefinitely, never presenting a gap greater than one. Attestations are signed as `{attester :: MemberId, sig}` with `sig` over `smpEncode ("SXGS", governanceId, govVersion, proposalHash)` — keyed on the canonical proposal, not on a certificate, for the reason given under `prevProposalHash` — with the attester's `MemberId` carried for key lookup as with votes. They are served in catch-up responses and relayable by anyone other than the presenter and the certificate's aye-signers: relaying keeps sparsely connected members live, and excluding the interested parties from the relay path denies any one of them sole control of the evidence — though what actually makes the evidence hard to fake is the multi-signer threshold, since a confederate's attestation can always be laundered through an uninvolved relay. What the attestation buys is not proof of honesty but *attribution*: a third-party-verifiable record of who vouched for enacting the result. For a genuinely enacted certificate, nay voters and abstainers applied it too and can attest; for a withheld one, an attester must out itself. Both conditions are waived only for certificates whose aye set is a strict majority both of the proposal's electorate and of the receiver's *current* settled membership — a banked majority that has since eroded gets no waiver, and a one-vote fabrication never qualifies. Falling short, the client does not apply, surfaces the pending state, and MAY offer user-confirmed adoption as a fallback. Version skipping means a member that cannot validate version N can still adopt N+1 directly, provided it holds N's bundle as a witness (step 1) — served alongside N+1's in the same catch-up response.
- A member that observes governance traffic referencing a version above its own sends
`x.grp.gov.request {governanceId, haveVersion}`. The version is directly readable from a proposal and from signed
membership events (in governed groups, signed `XGrpMemRole`/`XGrpMemDel` populate the existing `rosterVersion` field
with the sender's governance version, and `XGrpMemNew` gains an equivalent optional field); a certificate or
announcement carries no version, so for those the client first fetches the referenced proposal (bounded as in
transport rule 4) and compares. All catch-up requests (whatever triggered them) share one per-group rate limit with
backoff, and clients cap concurrent outstanding fetches per peer. The budget is partitioned by an observable
criterion: fetches for references that arrived over direct member connections have reserved capacity that forwarded or
unsigned references (announcements, votes by reference) cannot consume, with per-peer sub-budgets inside the reserve
(admins are themselves direct peers of most members, so a single hostile direct peer must not be able to drain it), so
reference spam cannot starve the objection path.
- Any member re-serves, per applied version above `haveVersion`, a bundle of the proposal and its **as-applied vote
set** (the union it evaluated at finality, a superset of the broadcast certificate). To bound reflected amplification,
a client serves a given requester only versions above what it last served them (as the roster machinery does with
`roster_served_version`), and rate-limits serving per requester over time. The response also includes the *hashes* of
any active proposals at the requester's new current version + 1 (full proposals are fetched on demand by
`proposalHash`, keeping responses bounded; retention allows up to one proposal per proposer, each shipping an
electorate list), so that a member advancing via catch-up regains the ability to vote in the live referendum instead
of being silently disenfranchised (it still counts in `E`, so its forced abstention would raise the bar for everyone).
- The catching-up member validates each bundle: the structural timing check, signatures, and electorate checks evaluated
against its recorded membership history at the bundle's `proposedAt` (settlement and removal times are recorded),
skipping the live plausibility check. The tally is evaluated over the bundle's votes **union** its own locally held
votes for that proposal, without a challenge window; the member is judging evidence of an outcome already final
elsewhere, and *that* premise must be established, not assumed: before adopting a bundle (or a stale certificate
routed here) that would advance or supersede its state, the client MUST apply the two load-bearing witnessed-chain
conditions: the **temporal link** (the target's `proposedAt` after the `expiresAt` of the referendum it applied at
its stored version, and the target's `expiresAt` in the past, both checkable from its own records) and the **frontier
bound** (several distinct eligible attesters, as defined there), *regardless of gap size*. A single-version step is
not exempt: without this, a two-member conspiracy could walk a victim forward one version at a time through this
window-free path, indefinitely, never presenting a gap greater than one. Attestations are signed as
`{attester :: MemberId, sig}` with `sig` over `smpEncode ("SXGS", governanceId, govVersion, proposalHash)` (keyed on
the canonical proposal, not on a certificate, for the reason given under `prevProposalHash`), with the attester's
`MemberId` carried for key lookup as with votes. They are served in catch-up responses and relayable by anyone other
than the presenter and the certificate's aye-signers: relaying keeps sparsely connected members live, and excluding
the interested parties from the relay path denies any one of them sole control of the evidence, though what actually
makes the evidence hard to fake is the multi-signer threshold, since a confederate's attestation can always be
laundered through an uninvolved relay. What the attestation buys is not proof of honesty but *attribution*: a
third-party-verifiable record of who vouched for enacting the result. For a genuinely enacted certificate, nay voters
and abstainers applied it too and can attest; for a withheld one, an attester must out itself. Both conditions are
waived only for certificates whose aye set is a strict majority both of the proposal's electorate and of the
receiver's *current* settled membership; a banked majority that has since eroded gets no waiver, and a one-vote
fabrication never qualifies. Falling short, the client does not apply, surfaces the pending state, and MAY offer
user-confirmed adoption as a fallback. Version skipping means a member that cannot validate version N can still adopt
N+1 directly, provided it holds N's bundle as a witness (step 1), served alongside N+1's in the same catch-up
response.
What recovery can and cannot do: it repairs version lag and divergence caused by *missing information* — a bundle can carry ayes the rejecter lacked, so a rejecter may accept on re-evaluation. It cannot force a member to accept a tally its own held votes still contradict — such a member stays behind until a later certificate it *can* validate arrives (which version skipping makes possible). And it is gated by connectivity: a member whose only reachable peers are the presenter and the certificate's aye-signers (host-only joiners, never-introduced pairs) cannot corroborate a certificate that does not qualify for the attestation waiver and stays pending — a liveness cost of the stale-mandate defense, stated under limitations. If group membership diverges substantially in the meantime (the stranded member rejects the new admins' membership events as unauthorized), later electorates may never validate for it; clients must surface this state to the user rather than mask it. Bundles are self-authenticating only to the extent the receiver's own records can verify them: for referenda predating the receiver's membership — the genesis certificate included — validation degrades to trust in the introducing host, as with all pre-join group state.
What recovery can and cannot do: it repairs version lag and divergence caused by *missing information*: a bundle can
carry ayes the rejecter lacked, so a rejecter may accept on re-evaluation. It cannot force a member to accept a tally
its own held votes still contradict; such a member stays behind until a later certificate it *can* validate arrives
(which version skipping makes possible). And it is gated by connectivity: a member whose only reachable peers are the
presenter and the certificate's aye-signers (host-only joiners, never-introduced pairs) cannot corroborate a certificate
that does not qualify for the attestation waiver and stays pending, a liveness cost of the stale-mandate defense,
stated under limitations. If group membership diverges substantially in the meantime (the stranded member rejects the
new admins' membership events as unauthorized), later electorates may never validate for it; clients must surface this
state to the user rather than mask it. Bundles are self-authenticating only to the extent the receiver's own records can
verify them: for referenda predating the receiver's membership (the genesis certificate included), validation degrades
to trust in the introducing host, as with all pre-join group state.
### Why incumbents cannot prevent a referendum in progress summary
### Why incumbents cannot prevent a referendum in progress: summary
- **Forge**: certificates require signatures from a biased majority of the electorate; admins do not hold members' keys.
- **Veto**: no admin signature or cooperation appears anywhere in propose/vote/apply, and concurrent decoy proposals cannot exclude anyone from voting on the genuine one (members vote per proposal, not per version).
- **Censor**: votes and certificates travel member→member over direct connections admins have no handle on (queue authority rests with the connection's own parties; residual router-level risk is the receiver's own chosen infrastructure, not the admins' — see limitations), and are forwardable by *anyone* for pairs lacking direct connections (transport rules 12).
- **Veto**: no admin signature or cooperation appears anywhere in propose/vote/apply, and concurrent decoy proposals
cannot exclude anyone from voting on the genuine one (members vote per proposal, not per version).
- **Censor**: votes and certificates travel member→member over direct connections admins have no handle on (queue
authority rests with the connection's own parties; residual router-level risk is the receiver's own chosen
infrastructure, not the admins'; see limitations), and are forwardable by *anyone* for pairs lacking direct
connections (transport rules 12).
- **Silence voters**: snapshot voting rights (rule 3) + removal deferral incl. the send-guard exemption (rule 4).
- **Rig the electorate mid-vote**: the electorate is fixed by the proposal at proposal time; role changes and blocks never affect eligibility.
- **Rush the vote**: the referendum period is structurally bound into the signed proposal; directly received proposals are plausibility-checked against broker timestamps; and every member gets at least the challenge window between seeing a non-unconditional result and applying it.
- **Mint an owner or pre-empt by deletion**: governed clients reject owner-role members outright, so `XGrpDel` and other owner-gated events have no valid sender.
- **Rig the electorate mid-vote**: the electorate is fixed by the proposal at proposal time; role changes and blocks
never affect eligibility.
- **Rush the vote**: the referendum period is structurally bound into the signed proposal; directly received proposals
are plausibility-checked against broker timestamps; and every member gets at least the challenge window between seeing
a non-unconditional result and applying it.
- **Mint an owner or pre-empt by deletion**: governed clients reject owner-role members outright, so `XGrpDel` and other
owner-gated events have no valid sender.
- **Turn governance off**: governance parameters have no update path.
What incumbents *can* still do is act before a referendum exists see limitations.
What incumbents *can* still do is act before a referendum exists; see limitations.
## Security analysis and limitations
- **Curated electorate / Sybil.** The electorate is the historically admin-curated member list; admins may have admitted sock puppets long before any vote ("join the same group several times... and pretend to be different members" — threat model). Opt-in groups accept this; `2024-03-14-super-peers.md` raises the same concern in passing, suggesting voting power weighted by "community score" to "compensate for anonymous participants who could subvert the vote if plain vote count was made" — an aside it explicitly calls out of scope, as it is here for v1.
- **Pre-proposal purges.** All anti-silencing guarantees are scoped to a referendum in progress. An incumbent who moves first can remove suspected dissidents *before* any proposal exists, shrinking the future electorate. Removals are broadcast events, so a purge is visible to the remaining members, who can respond by immediately proposing (any member can, at any time) — but members removed before that proposal are gone. The electorate tolerances (recently settled members and adjacent removals) widen this margin slightly; clients must surface both to voters, whose remedy is a nay. The structural fix is an authenticated membership log (step 2).
- **Withheld-proposal partition.** Colluders can distribute a proposal only among themselves and present proposal + certificate together at expiry. Victims receiving both via forwards accept the proposal (no timing check on forwards) and, under the late-voting rule, may cast fresh nays until their own challenge windows close. This defense depends on aggregation: the tally test is nonlinear, so a victim rejects only if the nays *it* holds at window close suffice (at `E = 100` against 40 colluding ayes, a member must hold ≥ 35 nays; one holding 30 adopts the coup). Aggregation in turn depends on certificate rebroadcast and vote fan-out reaching victims within overlapping windows — which the window-extension rule exists to maximize, and pre-existing partitions (below) can undermine. The realistic outcome is therefore a contested result whose boundary tracks connectivity: early isolated evaluators may adopt, later ones aggregate prior nays and reject. Members offline longer than the (extended) window return at finality and their late nays count nowhere. An unconditional certificate cannot be produced this way without a genuine majority; a large colluding minority plus engineered isolation of victims is the residual risk, superseded — like all divergence here — by the next referendum the victims can validate.
- **Membership equivocation.** Admins can have shown different membership states to different members *before* a proposal; those members then disagree on the electorate and fail closed — refusing the referendum rather than being manipulated — and recover via catch-up only if their records permit validation. Signing membership events makes equivocation evidence-producing but not impossible. The proper fix is a channels-style authenticated roster; see future work.
- **Benign electorate races.** Membership changes in flight when a proposal is made can cause honest members to reject it (check 3). The settled-member rule plus the settlement-skew tolerance absorb join races (settlement is a per-client observation and propagates slowly); the removal tolerance absorbs removal races; catch-up repairs stragglers. Residual rejections cost the proposer a retry at the next version, not a partition.
- **Pre-existing partitions.** Pairs the admins never introduced (including the documented concurrent-invite race in `2025-11-24-member-relations-vector.md`) still cannot exchange votes directly; the any-member forwarding rule reduces, but does not eliminate, dependence on connectivity the incumbents shaped.
- **Router-level censorship.** A vote's delivery to member X depends on the SMP routers X chose for their receiving queues, which can drop a queue's messages wholesale (SMP threat model; undetectable *selective* dropping is excluded there, so censorship of individual votes is not available to a router). This is orthogonal to admin power, is mitigated by queue redundancy and rotation, and — unlike admin forwarding — is not correlated with the parties the referendum acts against; it is listed here because "cannot censor" claims must be scoped to the chat layer.
- **Knife-edge divergence.** Non-unconditional certificates can resolve differently across members whose challenge windows saw different vote sets. Unconditional certificates are exposed only through vote equivocation: a confederate can vote aye into the certificate and reveal a conflicting nay to selected members, annulling the aye there; if the certificate's margin over `E/2` is within the number of such reveals, members diverge on a certificate class that otherwise applies immediately. Clients may therefore hold unconditional certificates with margin ≤ a small constant for the challenge window as well. Version lag from such divergence is repairable via catch-up; genuine tally disagreement persists until a later referendum the stranded member can validate (see "Catch-up and recovery") — still strictly better than the status quo, where any admin state change can diverge arbitrarily, permanently, and undetectably.
- **Proposal spam.** Any member can propose, and proposals can coexist per version; retention is protocol-bounded at one proposal per proposer per version, above-version proposals are not retained at all, and clients additionally rate-limit per member (UI-level); a spammer can be removed by admins or voted out with their own mechanism. Decoys cannot lock members out of genuine votes (per-proposal voting) and cannot win without passing the tally.
- **Moderation friction from removal deferral.** While a referendum is active, removals of electorate members do not sever connections; at parameter ceilings the deferral can last ~120 days (~22 days at defaults: period + freshness horizon + window + extension cap); tier-(ii) deferral (reference-only, unscoped) is separately budgeted per originating peer and capped (see rule 4 and open question 6). The exposure is narrow — deferred connections carry only `x.grp.gov.*` events, `blockedByAdmin` still suppresses everything else, and the removal takes full effect when the referendum resolves — but a removed member who proposed before removal keeps a live governance channel for the referendum's bounded lifetime. This is the deliberate price of making mid-vote purges ineffective.
- **Stale mandates.** A passing certificate could be withheld and enacted later — including, for an unconditional certificate, a banked majority mandate enacted after that majority has eroded. The hard bound is version monotonicity together with the witnessed-chain rule: a reserve cannot be banked above the version the group can actually reach, since acceptance beyond a one-version gap is bounded by the frontier independently attested by several members from outside the certificate's aye set, and a completed referendum retires every reserve strictly below its version. A banked competitor at the *same* version can contest only that one round — under mandate order it prevails only by out-polling the live certificate in genuine ayes (equal ayes fall through to the unconditionality tie-break), which a minority cannot do against a well-supported **reaffirmation referendum** (proposing the current admin set — already expressible as `GAReplaceAdmins`). Clients should offer reaffirmation; flushing all suspected reserves takes at most two successive referenda. The freshness horizon and attestation requirement do not make late enactment impossible — one abstaining confederate can attest, and certificates whose ayes are still a majority of the receiver's current membership are waived — but they make it slow, detectable, and attributable: a stale enactment arrives through the rate-limited catch-up path carrying a signed record of who vouched for it. The same rules govern stale same-version competitors, with a corroboration liveness cost for sparsely connected members (see "Catch-up and recovery").
- **Metadata.** `governanceId` is a shared random group identifier appearing only inside e2e-encrypted messages. Vote signatures are third-party-verifiable *within the group by design* — members can prove to each other how someone voted. Groups wanting ballot secrecy need a different scheme (blind/ring signatures); explicitly out of scope.
- **Curated electorate / Sybil.** The electorate is the historically admin-curated member list; admins may have admitted
sock puppets long before any vote ("join the same group several times... and pretend to be different members", threat
model). Opt-in groups accept this; `2024-03-14-super-peers.md` raises the same concern in passing, suggesting voting
power weighted by "community score" to "compensate for anonymous participants who could subvert the vote if plain vote
count was made", an aside it explicitly calls out of scope, as it is here for v1.
- **Pre-proposal purges.** All anti-silencing guarantees are scoped to a referendum in progress. An incumbent who moves
first can remove suspected dissidents *before* any proposal exists, shrinking the future electorate. Removals are
broadcast events, so a purge is visible to the remaining members, who can respond by immediately proposing (any member
can, at any time); but members removed before that proposal are gone. The electorate tolerances (recently settled
members and adjacent removals) widen this margin slightly; clients must surface both to voters, whose remedy is a nay.
The structural fix is an authenticated membership log (step 2).
- **Withheld-proposal partition.** Colluders can distribute a proposal only among themselves and present proposal +
certificate together at expiry. Victims receiving both via forwards accept the proposal (no timing check on forwards)
and, under the late-voting rule, may cast fresh nays until their own challenge windows close. This defense depends on
aggregation: the tally test is nonlinear, so a victim rejects only if the nays *it* holds at window close suffice (at
`E = 100` against 40 colluding ayes, a member must hold ≥ 35 nays; one holding 30 adopts the coup). Aggregation in
turn depends on certificate rebroadcast and vote fan-out reaching victims within overlapping windows, which the
window-extension rule exists to maximize, and pre-existing partitions (below) can undermine. The realistic outcome is
therefore a contested result whose boundary tracks connectivity: early isolated evaluators may adopt, later ones
aggregate prior nays and reject. Members offline longer than the (extended) window return at finality and their late
nays count nowhere. An unconditional certificate cannot be produced this way without a genuine majority; a large
colluding minority plus engineered isolation of victims is the residual risk, superseded (like all divergence here)
by the next referendum the victims can validate.
- **Membership equivocation.** Admins can have shown different membership states to different members *before* a
proposal; those members then disagree on the electorate and fail closed (refusing the referendum rather than being
manipulated) and recover via catch-up only if their records permit validation. Signing membership events makes
equivocation evidence-producing but not impossible. The proper fix is a channels-style authenticated roster; see
future work.
- **Benign electorate races.** Membership changes in flight when a proposal is made can cause honest members to reject
it (check 3). The settled-member rule plus the settlement-skew tolerance absorb join races (settlement is a per-client
observation and propagates slowly); the removal tolerance absorbs removal races; catch-up repairs stragglers. Residual
rejections cost the proposer a retry at the next version, not a partition.
- **Pre-existing partitions.** Pairs the admins never introduced (including the documented concurrent-invite race in
`2025-11-24-member-relations-vector.md`) still cannot exchange votes directly; the any-member forwarding rule reduces,
but does not eliminate, dependence on connectivity the incumbents shaped.
- **Router-level censorship.** A vote's delivery to member X depends on the SMP routers X chose for their receiving
queues, which can drop a queue's messages wholesale (SMP threat model; undetectable *selective* dropping is excluded
there, so censorship of individual votes is not available to a router). This is orthogonal to admin power, is
mitigated by queue redundancy and rotation, and (unlike admin forwarding) is not correlated with the parties the
referendum acts against; it is listed here because "cannot censor" claims must be scoped to the chat layer.
- **Knife-edge divergence.** Non-unconditional certificates can resolve differently across members whose challenge
windows saw different vote sets. Unconditional certificates are exposed only through vote equivocation: a confederate
can vote aye into the certificate and reveal a conflicting nay to selected members, annulling the aye there; if the
certificate's margin over `E/2` is within the number of such reveals, members diverge on a certificate class that
otherwise applies immediately. Clients may therefore hold unconditional certificates with margin ≤ a small constant
for the challenge window as well. Version lag from such divergence is repairable via catch-up; genuine tally
disagreement persists until a later referendum the stranded member can validate (see "Catch-up and recovery"); still
strictly better than the status quo, where any admin state change can diverge arbitrarily, permanently, and
undetectably.
- **Proposal spam.** Any member can propose, and proposals can coexist per version; retention is protocol-bounded at one
proposal per proposer per version, above-version proposals are not retained at all, and clients additionally
rate-limit per member (UI-level); a spammer can be removed by admins or voted out with their own mechanism. Decoys
cannot lock members out of genuine votes (per-proposal voting) and cannot win without passing the tally.
- **Moderation friction from removal deferral.** While a referendum is active, removals of electorate members do not
sever connections; at parameter ceilings the deferral can last ~120 days (~22 days at defaults: period + freshness
horizon + window + extension cap); tier- (ii) deferral (reference-only, unscoped) is separately budgeted per
originating peer and capped (see rule 4 and open question 6). The exposure is narrow: deferred connections carry only
`x.grp.gov.*` events, `blockedByAdmin` still suppresses everything else, and the removal takes full effect when the
referendum resolves, but a removed member who proposed before removal keeps a live governance channel for the
referendum's bounded lifetime. This is the deliberate price of making mid-vote purges ineffective.
- **Stale mandates.** A passing certificate could be withheld and enacted later, including, for an unconditional
certificate, a banked majority mandate enacted after that majority has eroded. The hard bound is version monotonicity
together with the witnessed-chain rule: a reserve cannot be banked above the version the group can actually reach,
since acceptance beyond a one-version gap is bounded by the frontier independently attested by several members from
outside the certificate's aye set, and a completed referendum retires every reserve strictly below its version. A
banked competitor at the *same* version can contest only that one round; under mandate order it prevails only by
out-polling the live certificate in genuine ayes (equal ayes fall through to the unconditionality tie-break), which a
minority cannot do against a well-supported **reaffirmation referendum** (proposing the current admin set, already
expressible as `GAReplaceAdmins`). Clients should offer reaffirmation; flushing all suspected reserves takes at most
two successive referenda. The freshness horizon and attestation requirement do not make late enactment impossible (one
abstaining confederate can attest, and certificates whose ayes are still a majority of the receiver's current
membership are waived), but they make it slow, detectable, and attributable: a stale enactment arrives through the
rate-limited catch-up path carrying a signed record of who vouched for it. The same rules govern stale same-version
competitors, with a corroboration liveness cost for sparsely connected members (see "Catch-up and recovery").
- **Metadata.** `governanceId` is a shared random group identifier appearing only inside e2e-encrypted messages. Vote
signatures are third-party-verifiable *within the group by design*: members can prove to each other how someone
voted. Groups wanting ballot secrecy need a different scheme (blind/ring signatures); explicitly out of scope.
## Alternatives considered
- **Fixed majority of the electorate.** Subsumed: it is exactly the "unconditional certificate" case; AQB additionally lets lower-turnout referenda resolve, at the cost of the challenge window.
- **Interactive consensus (BFT / owner DAG voting)** as sketched in `2023-10-20-group-integrity.md` — rejected there already as impractical for mobile clients; unnecessary here since a one-shot threshold certificate suffices.
- **Approvals among privileged members only** (`2024-04-01-super-peers-2.md`) — solves accidental/destructive actions among peers, not member sovereignty; its `MemberApproval` shape is reused here in spirit for certificate entries.
- **Aggregate signatures (BLS)** to shrink certificates — avoided: new crypto dependency; Ed25519 vote lists up to a few hundred members fit the existing chunked-blob transport.
See "Related work" at the end of this document for how these choices sit against the literature, and
`2026-08-01-group-governance-related-work.md` for the full analysis.
- **Fixed majority of the electorate.** Subsumed: it is exactly the "unconditional certificate" case; AQB additionally
lets lower-turnout referenda resolve, at the cost of the challenge window.
- **Interactive consensus (BFT / owner DAG voting)** as sketched in `2023-10-20-group-integrity.md`: rejected there
already as impractical for mobile clients; unnecessary here since a one-shot threshold certificate suffices.
- **Approvals among privileged members only** (`2024-04-01-super-peers-2.md`): solves accidental/destructive actions
among peers, not member sovereignty; its `MemberApproval` shape is reused here in spirit for certificate entries.
- **Aggregate signatures (BLS)** to shrink certificates, avoided: new crypto dependency; Ed25519 vote lists up to a few
hundred members fit the existing chunked-blob transport.
## Implementation sketch
- `Protocol.hs`: `GovAction`, enable/proposal/vote/cert/request types, five event tags (+ `isForwardedGroupMsg`); deterministic binary encodings with domain-separation tags (`SXGG`/`SXGP`/`SXGV`, plus `SXGS` state attestations served in catch-up responses).
- Key management: per-group member keypair for p2p governed groups (`group_members.member_pub_key` exists; add p2p private-key storage and population of `memberPubKey` on join/intro; TOFU pinning as in `applyMemberKeyRole`).
- Optionally in the same version: populate and verify signatures on `XGrpMemNew`/`XGrpMemDel`/`XGrpMemRole` in governed p2p groups via the existing p2p verification branch in `withVerifiedMsg` (equivocation evidence; independently closes the unsigned-forward forgery hole). In governed groups these events also carry the sender's governance version — reusing the existing `rosterVersion` field on `XGrpMemRole`/`XGrpMemDel`, plus an equivalent optional field on `XGrpMemNew` — which is the catch-up trigger for members with no other governance traffic.
- `Subscriber.hs`: handlers for the five events; genesis parameter-bounds validation; proposal timing validation and per-proposer retention cap; certificate validation + challenge-window worker with late-voting support; version-gated apply with witnessed-chain version skipping, the same-version mandate-order exception, and the post-apply compact announcement; catch-up serving from stored bundles with per-requester served-version bound and rate limiting; forwarder-check and `blockedByAdmin` exemptions; removal deferral incl. the governance send-guard exemption in `xGrpMemDel` and the send path; **rejection of owner-role members in `xGrpMemNew`/`xGrpMemIntro`/`xGrpMemFwd`/`xGrpMemRole` and owner-role invitations for governed groups**; relax `XGrpInfo`/`XGrpPrefs` receiver gates to `GRAdmin` for governed groups.
- `Commands.hs`: relax the `GROwner` assertion in `runUpdateGroupProfile` (and its callers) to `GRAdmin` for governed groups; new APIs below.
- `Protocol.hs`: `GovAction`, enable/proposal/vote/cert/request types, five event tags (+ `isForwardedGroupMsg`);
deterministic binary encodings with domain-separation tags (`SXGG`/`SXGP`/`SXGV`, plus `SXGS` state attestations
served in catch-up responses).
- Key management: per-group member keypair for p2p governed groups (`group_members.member_pub_key` exists; add p2p
private-key storage and population of `memberPubKey` on join/intro; TOFU pinning as in `applyMemberKeyRole`).
- Optionally in the same version: populate and verify signatures on `XGrpMemNew`/`XGrpMemDel`/`XGrpMemRole` in governed
p2p groups via the existing p2p verification branch in `withVerifiedMsg` (equivocation evidence; independently closes
the unsigned-forward forgery hole). In governed groups these events also carry the sender's governance version
(reusing the existing `rosterVersion` field on `XGrpMemRole`/`XGrpMemDel`, plus an equivalent optional field on
`XGrpMemNew`), which is the catch-up trigger for members with no other governance traffic.
- `Subscriber.hs`: handlers for the five events; genesis parameter-bounds validation; proposal timing validation and
per-proposer retention cap; certificate validation + challenge-window worker with late-voting support; version-gated
apply with witnessed-chain version skipping, the same-version mandate-order exception, and the post-apply compact
announcement; catch-up serving from stored bundles with per-requester served-version bound and rate limiting;
forwarder-check and `blockedByAdmin` exemptions; removal deferral incl. the governance send-guard exemption in
`xGrpMemDel` and the send path; **rejection of owner-role members in `xGrpMemNew`/`xGrpMemIntro`/`xGrpMemFwd`/
`xGrpMemRole` and owner-role invitations for governed groups**; relax `XGrpInfo`/`XGrpPrefs` receiver gates to
`GRAdmin` for governed groups.
- `Commands.hs`: relax the `GROwner` assertion in `runUpdateGroupProfile` (and its callers) to `GRAdmin` for governed
groups; new APIs below.
- Store:
```sql
ALTER TABLE groups ADD COLUMN governance TEXT; -- params + governanceId; null = not governed
ALTER TABLE groups ADD COLUMN governance_version INTEGER;
ALTER TABLE group_members ADD COLUMN settled_at TEXT; -- when this client saw the member connect;
-- backfilled from created_at for members already at GSMemConnected or beyond at migration time
ALTER TABLE group_members ADD COLUMN gov_served_version INTEGER; -- catch-up amplification bound
CREATE TABLE group_referenda (
referendum_id INTEGER PRIMARY KEY,
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
proposal_hash BLOB NOT NULL,
gov_version INTEGER NOT NULL,
action BLOB NOT NULL,
electorate BLOB NOT NULL, -- member id list + hash; retained for catch-up serving
prev_proposal_hash BLOB NOT NULL,
proposed_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
proposer_member_id BLOB NOT NULL,
proposal_sig BLOB NOT NULL,
status TEXT NOT NULL, -- active / passed / failed / superseded / witnessed (chain evidence, not applied)
applied_cert BLOB, -- as-applied vote set, re-served on catch-up
attestations BLOB -- third-party state attestations held for serving
ALTER TABLE groups
ADD COLUMN governance TEXT; -- params + governanceId; null = not governed
ALTER TABLE groups
ADD COLUMN governance_version INTEGER;
ALTER TABLE group_members
ADD COLUMN settled_at TEXT;
-- when this client saw the member connect;
-- backfilled from created_at for members already at GSMemConnected or beyond at migration time
ALTER TABLE group_members
ADD COLUMN gov_served_version INTEGER; -- catch-up amplification bound
CREATE TABLE group_referenda
(
referendum_id INTEGER PRIMARY KEY,
group_id INTEGER NOT NULL REFERENCES groups ON DELETE CASCADE,
proposal_hash BLOB NOT NULL,
gov_version INTEGER NOT NULL,
action BLOB NOT NULL,
electorate BLOB NOT NULL, -- member id list + hash; retained for catch-up serving
prev_proposal_hash BLOB NOT NULL,
proposed_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
proposer_member_id BLOB NOT NULL,
proposal_sig BLOB NOT NULL,
status TEXT NOT NULL, -- active / passed / failed / superseded / witnessed (chain evidence, not applied)
applied_cert BLOB, -- as-applied vote set, re-served on catch-up
attestations BLOB -- third-party state attestations held for serving
);
CREATE TABLE group_referendum_votes (
referendum_id INTEGER NOT NULL REFERENCES group_referenda ON DELETE CASCADE,
group_member_id INTEGER NOT NULL,
vote TEXT NOT NULL,
vote_sig BLOB NOT NULL,
annulled INTEGER NOT NULL DEFAULT 0
CREATE TABLE group_referendum_votes
(
referendum_id INTEGER NOT NULL REFERENCES group_referenda ON DELETE CASCADE,
group_member_id INTEGER NOT NULL,
vote TEXT NOT NULL,
vote_sig BLOB NOT NULL,
annulled INTEGER NOT NULL DEFAULT 0
);
```
- API: `APIEnableGroupGovernance`, `APIProposeGroupAdmins`, `APIGroupVote`; certificate assembly, application, and catch-up are automatic. Chat items for proposal / votes / result / contested result, styled like existing group events.
- API: `APIEnableGroupGovernance`, `APIProposeGroupAdmins`, `APIGroupVote`; certificate assembly, application, and
catch-up are automatic. Chat items for proposal / votes / result / contested result, styled like existing group
events.
## Future work
- **Relay groups / channels.** The same certificate machinery can gate the channel roster (replace the `== GROwner` checks in `xGrpRoster`/`applyAtRosterVersion` with certificate validation) — but replacing *owners* there additionally requires threshold or majority updates to the short-link owner chain (`OwnerAuth` in the agent protocol) and link-queue `RKEY` authority in simplexmq, where owners are currently ranked so the creator cannot be demoted, and any single owner key suffices to update the link — multisig having been considered and deferred there. That is the step-2 RFC, and it is where this design meets the project's stated roadmap item "Multisig: M-of-N approval for administrative actions" (`docs/protocol/channels-overview.md`). An authenticated membership log (roster) would also retire the electorate-equivocation and pre-proposal-purge limitations above.
- **More referendum actions**: change governance parameters (including disabling), update profile/preferences, delete the group, replace moderators.
- **Governance for ownerless legacy groups** (no one left to sign the genesis certificate): possibly unanimous-member enabling.
- **Relay groups / channels.** The same certificate machinery can gate the channel roster (replace the `== GROwner`
checks in `xGrpRoster`/`applyAtRosterVersion` with certificate validation); but replacing *owners* there additionally
requires threshold or majority updates to the short-link owner chain (`OwnerAuth` in the agent protocol) and
link-queue `RKEY` authority in simplexmq, where owners are currently ranked so the creator cannot be demoted, and any
single owner key suffices to update the link, multisig having been considered and deferred there. That is the step-2
RFC, and it is where this design meets the project's stated roadmap item "Multisig: M-of-N approval for administrative
actions" (`docs/protocol/channels-overview.md`). An authenticated membership log (roster) would also retire the
electorate-equivocation and pre-proposal-purge limitations above.
- **More referendum actions**: change governance parameters (including disabling), update profile/preferences, delete
the group, replace moderators.
- **Governance for ownerless legacy groups** (no one left to sign the genesis certificate): possibly unanimous-member
enabling.
- **Reputation-weighted or Sybil-resistant voting**, per `2024-03-14-super-peers.md`.
- **Ballot secrecy** (blind or ring signatures) for groups that need it.
## Open questions
1. Should v1 ship `GAChangeGovernance` (at least for disabling governance) rather than making enabling a one-way door?
2. Challenge-window default: 24h assumed; mobile-offline patterns may argue for longer (matching the 7-day referendum period's tolerance).
2. Challenge-window default: 24h assumed; mobile-offline patterns may argue for longer (matching the 7-day referendum
period's tolerance).
3. Should the genesis certificate require unanimity of owners (current design) or majority of owners?
4. Whether to sign membership events in governed p2p groups in v1 (recommended here) or defer to a separate signing rollout.
5. The electorate tolerances (settlement skew, in-flight removals) trade benign-race robustness against proposer/incumbent discretion over the electorate's margins; is surfacing them to voters sufficient, or should the tolerances be zero (stricter, more benign rejections)?
6. The tier-(ii) deferral and catch-up fetch budgets are two-sided DoS tuning: too tight, and purges become schedulable into predictably unprotected gaps; too loose, and reference spam suspends moderation or starves the objection path. The witnessed chain adds a third: serving and storing O(gap) bundles is itself an amplification surface, which an absolute cap on the accepted gap would bound along with the cost of fabricating chains — at the price of leaving very long-absent members to rejoin rather than catch up. All three need adversarial analysis with concrete constants before implementation.
4. Whether to sign membership events in governed p2p groups in v1 (recommended here) or defer to a separate signing
rollout.
5. The electorate tolerances (settlement skew, in-flight removals) trade benign-race robustness against
proposer/incumbent discretion over the electorate's margins; is surfacing them to voters sufficient, or should the
tolerances be zero (stricter, more benign rejections)?
6. The tier- (ii) deferral and catch-up fetch budgets are two-sided DoS tuning: too tight, and purges become schedulable
into predictably unprotected gaps; too loose, and reference spam suspends moderation or starves the objection path.
The witnessed chain adds a third: serving and storing O (gap) bundles is itself an amplification surface, which an
absolute cap on the accepted gap would bound along with the cost of fabricating chains, at the price of leaving very
long-absent members to rejoin rather than catch up. All three need adversarial analysis with concrete constants
before implementation.
7. Should certificate *acceptance* be made a pure function of the certificate, as ranking already is? It is currently
the design's one deviation from strong eventual consistency and the direct cause of knife-edge divergence; see
"Related work".
## Related work
Full analysis in [`2026-08-01-group-governance-related-work.md`](2026-08-01-group-governance-related-work.md); this is
the summary.
**Whether this is possible at all.** Under Herlihy's consensus hierarchy
([Wait-Free Synchronization](https://cs.brown.edu/~mph/Herlihy91/p124-herlihy.pdf), TOPLAS 13 (1), 1991), Frey, Gestin
and Raynal compute the synchronization power of access-control objects
([The Synchronization Power of AllowList and DenyList](https://arxiv.org/abs/2302.06344), DISC
2023, [doi:10.4230/LIPIcs.DISC.2023.39](https://doi.org/10.4230/LIPIcs.DISC.2023.39)): an AllowList has consensus number
1, a *k*-DenyList has consensus number *k*, and the entire difference is the **anti-flickering** property: once denied,
never allowed again. Promoting admins is AllowList-shaped and free; demoting them with revocation semantics would be
DenyList-shaped, and since every member verifies admin authority, *k* is the whole group, unattainable asynchronously
by [FLP](https://doi.org/10.1145/3149.214121) (Fischer, Lynch & Paterson, JACM 32 (2), 1985). **We therefore decline
anti-flickering deliberately**: a demoted admin can be re-recognized, by a later referendum and transiently during a
contested window. Revisable finality is not a compromise, it is the price of implementability, and the RFC accordingly
never claims revocation. The same shape holds for payments
in [The Consensus Number of a Cryptocurrency](https://arxiv.org/abs/1906.05574) (Guerraoui et al., PODC 2019).
**What consistency model this is.** [Byzantine Eventual Consistency](https://arxiv.org/abs/2012.00472) (Kleppmann &
Howard, 2020) characterizes the boundary by I-confluence and suggests exactly our split: aggregate I-confluently, then
decide the winner. Our vote accumulation is I-confluent (the annulment rule is defined to keep union order-independent);
the winner is decided by mandate order instead of consensus. One honest gap: strong eventual consistency
([Shapiro et al.](https://inria.hal.science/inria-00555588), 2011) requires state to be a function of received updates,
and while our *ranking* is pinned to canonical bytes, *acceptance* still unions locally held votes. That deviation is
the root of knife-edge divergence, and closing it would trade the anti-vote-withholding defence for convergence (open
question 7).
**Duelling admins.** [ERA](https://arxiv.org/abs/2601.22963) (Dougal, PaPoC
'26, [doi:10.1145/3806077.3806691](https://doi.org/10.1145/3806077.3806691)), from Element/Matrix, is the closest
published work: two admins concurrently revoking each other, where revocation is non-monotonic and forces rollbacks. Its
critique of Kleppmann's *seniority ranking* (a junior can never revoke a senior, and a revoked admin can backdate to
fake concurrency and undo their own demotion) applies directly to SimpleX's existing `roleRequiredToChange`, and is an
argument for this RFC. **We remove the duel rather than arbitrate it**: authority comes from a majority certificate
rather than from another admin, and the whole set is replaced atomically, so there is no revocation cycle. **We reject
its finality arbiter**: a mutually trusted ordering peer is the chokepoint this design exists to remove, and its
fallback of a "Creator" arbiter is unacceptable when the creator may be who the group needs to remove. Consequently we
do not get ERA's bounded total order. Its backdating analysis also confirms that our witnessed-chain temporal rules
raise cost rather than establish a bound.
**Practice in group messaging.** [MLS](https://www.rfc-editor.org/rfc/rfc9420.html) (RFC 9420) advances linear epochs
and lets the Delivery Service serialize concurrent commits, unavailable to us by construction, which is why we need
mandate order where MLS needs only a server. [DCGKA](https://doi.org/10.1145/3460120.3484542) (Weidner, Kleppmann,
Hugenroth & Beresford, CCS 2021; [eprint 2020/1281](https://eprint.iacr.org/2020/1281)) is serverless and explicitly
tolerates the same flickering: "a user may be removed and re-added, possibly indirectly (e.g., due to a remove message
'undoing' a concurrent remove)", and scopes authorization policy out, which is the gap this RFC
fills. [More is Less](https://eprint.iacr.org/2017/713) (Rösler, Mainka & Schwenk, EuroS&P 2018) found group management
messages unauthenticated in deployed messengers, which is the empirical case for signing membership events here.
**The voting rule.** Positive turnout bias is from [Polkadot](https://arxiv.org/abs/2005.13456) (Burdges et al.): "in
case of low turnout we favour the nay side, or status quo, by requiring a super-majority approval, and as turnout
approaches 100% the requirement dials down to majority-carries", justified by status-quo safety and the volatility of
narrow low-turnout results. **We prefer it to a fixed quorum** because participation quorums reward abstention
(opponents defeat a proposal more cheaply by boycotting than by voting nay) and because on-chain governance shows
turnout far too low for fixed quorums to be met ([Feichtinger et al.](https://arxiv.org/abs/2302.12125); [Fritsch et
al.](https://arxiv.org/abs/2204.01176)). AQB's weakness is that with zero nays any non-empty aye set passes, so our
protection is procedural: the enforced period and challenge window, during which a single nay raises the bar steeply.
**We reject ballot secrecy** ([Juels, Catalano & Jakobsson](https://doi.org/10.1145/1102199.1102213), WPES 2005) because
verifiable signatures are what make a certificate self-authenticating and able to travel past a hostile forwarder, at
the cost, acknowledged in the limitations, that incumbents can identify nay voters.
**Sybil resistance and accountability.** [The Sybil Attack](https://doi.org/10.1007/3-540-45748-8_24) (Douceur, IPTPS
2002) states the limit we inherit: without a central authority, Sybils are always possible, and our electorate is an
admin-curated list. Out of scope for v1. Our attestations are a weak instance of accountability in the sense
of [PeerReview](https://doi.org/10.1145/1294261.1294279) (Haeberlen, Kouznetsov & Druschel, SOSP 2007): signed
statements, not complete logs with witness coverage, so we claim attribution, not detection completeness; and
unlike [Casper](https://arxiv.org/abs/1710.09437) (Buterin & Griffith, 2017) there is no stake to slash, leaving social
enforcement, which ERA independently concludes as well. Finally, **we are deliberately not fork-consistent**:
fork-linearizability ([Mazières & Shasha](https://doi.org/10.1145/571825.571840), PODC
2002; [SUNDR](https://www.usenix.org/conference/osdi-04/secure-untrusted-data-repository-sundr), OSDI
2004; [Cachin et al.](https://doi.org/10.1145/1281100.1281121), PODC 2007) makes divergence permanent so it is
detectable, whereas our supersede and catch-up rules exist to re-merge diverged members. That is the right trade for a
chat group, but this document does not borrow that vocabulary.