Trust & Neighborhoods
Communities in Mehr are emergent, not declared. There are no admin-created "zones" to join, no governance to negotiate, no artificial boundaries between groups. Instead, communities form naturally from a trust graph — just like in the real world.
The Trust Graph
Each node maintains a set of trusted peers:
TrustConfig {
// Peers I trust — relay their traffic for free
trusted_peers: Set<NodeID>,
// What I charge non-trusted traffic
default_cost_per_byte: u64,
// Per-peer cost overrides (discount for friends-of-friends, etc.)
cost_overrides: Map<NodeID, u64>,
// Self-assigned scopes — geographic and interest (purely informational)
scopes: Vec<HierarchicalScope>, // max 8 per node
// DEPRECATED: use scopes instead. Kept for backward compatibility.
community_label: Option<String>, // e.g., "portland-mesh"
}
Adding a trusted peer is the only social action in Mehr. Everything else — free local communication, community identity, credit lines — emerges from the trust graph.
Trust relationships are asymmetric and revocable at any time. Removing a node from trusted_peers immediately ends free relay for that node and downgrades any stored data from "trusted peer" to normal priority in the garbage collection policy. Cost overrides are unidirectional — they apply to outbound traffic pricing from the configuring node only.
How Communities Emerge
When a cluster of nodes all trust each other, a neighborhood forms:
[Alice] ←──trust──→ [Bob] ←──trust──→ [Carol]
│ │ │
└────trust────→ [Dave] ←───trust──────┘
Alice, Bob, Carol, and Dave are a neighborhood. No one "created" it. No one "joined" it. It exists because they trust each other.
Properties
- No admin: Nobody runs the neighborhood. It has no keys, no governance, no admission policy.
- No fragmentation: The trust graph is continuous. There are no hard boundaries between communities — neighborhoods overlap naturally when people have friends in multiple clusters.
- No UX burden: You just mark people as trusted. The same action you'd take when adding a contact.
- Fully decentralized: There is nothing to attack, take over, or censor.
Free Local Communication
Traffic between trusted peers is always free. A relay node checks its trust list:
Relay decision:
if sender is trusted AND destination is trusted:
relay for free (no lottery, no channel update)
else if sender is trusted:
relay for free (helping a friend send outbound)
else:
relay with stochastic reward lottery
Note the asymmetry: a relay helps its trusted peers send traffic for free, but does not relay free traffic from strangers just because the destination is trusted. Without this rule, an untrusted node could route unlimited free traffic through you to any of your trusted peers, shifting relay costs onto you without compensation.
This means a village mesh where everyone trusts each other operates with zero economic overhead — no tokens, no channels, no settlements. The economic layer only activates for traffic crossing trust boundaries.
Trust-Based Credit
When a node needs MHR (e.g., to reach beyond its trusted neighborhood), its trusted peers can vouch for it:
Transitive credit:
Direct trust: full credit line (set by trusting peer)
Friend-of-friend (2 hops): 10% of direct credit line
3+ hops of trust: no credit (too diluted)
If a credited node defaults, the vouching peer absorbs the debt.
This makes trust economically meaningful — you only trust people
you'd lend to.
The credit line is rate-limited for safety:
| Trust Distance | Max Credit | Rate Limit |
|---|---|---|
| Direct trusted peer | Configurable by trusting node | Per-epoch (configurable) |
| Friend-of-friend | 10% of direct limit | Per-epoch, per friend-of-friend |
| Beyond 2 hops | None | N/A |
Credit accounting:
Each trusting node tracks outstanding credit per grantee:
CreditState {
grantee: NodeID,
credit_limit: u64, // max outstanding μMHR
outstanding: u64, // currently extended
granted_this_epoch: u64, // epoch-scoped rate limit
last_grant_epoch: u64, // for epoch-boundary reset
}
Rules:
- Direct peers: each gets a separate credit_limit (set in TrustConfig)
- Friend-of-friend: each gets 10% of the vouching peer's direct limit,
tracked independently per grantee
- granted_this_epoch resets to 0 at each epoch boundary
- Outstanding credit that exceeds limit: no new grants until repaid
- Default handling: vouching peer's outstanding balance increases by
the defaulted amount (absorbs debt); grantee is flagged
Hierarchical Scopes
Nodes self-assign scopes that describe where they are and what they care about. Scopes replace the flat community_label with a hierarchical system that supports both geographic locality and interest communities.
HierarchicalScope {
scope_type: enum {
Geo, // physical location hierarchy
Topic, // interest/community hierarchy
},
segments: Vec<String>, // hierarchical path, max 8 levels, max 32 chars each
}
Geographic Scopes
Geographic scopes describe physical location, from broad to narrow:
Alice sets: Geo("north-america", "us", "oregon", "portland", "hawthorne")
Bob sets: Geo("north-america", "us", "oregon", "portland", "pearl")
Carol sets: Geo("asia", "iran", "tehran", "district-6")
The hierarchy is bottom-up — neighbors form the base, cities emerge from connected neighborhoods, regions from connected cities. This mirrors how mesh networks physically grow: you connect to the person next door first, then the neighborhood, then the city.
Interest Scopes
Interest scopes describe communities of shared interest that span geography:
Dave sets: Topic("gaming", "pokemon", "competitive")
Eve sets: Topic("science", "physics", "quantum")
Frank sets: Topic("music", "jazz")
Interest communities are sparse — not everyone in Portland cares about Pokemon. A Pokemon community might span Portland, Tokyo, and Berlin with nothing in between. This is the opposite of geographic scopes, which are dense (most people are physically somewhere).
Scope Matching
Subscriptions and queries can match at any level of the hierarchy:
| Pattern | Matches |
|---|---|
Geo("north-america", "us", "oregon", "portland") exact | Only Portland |
Geo("north-america", "us", "oregon") prefix | Portland, Eugene, Bend, and everything in Oregon |
Geo("north-america", "us") prefix | All US scopes |
Topic("gaming") prefix | Pokemon, Minecraft, and all gaming sub-topics |
Topic("gaming", "pokemon") exact | Only Pokemon, not gaming broadly |
Properties
Scopes retain all the properties of the old community_label:
- Self-assigned — no one approves your scope claims, no authority enforces them
- Not authoritative — scopes carry no protocol-level privileges (cannot grant access, waive fees, or modify trust)
- Not unique — multiple disjoint clusters can use the same scope
- Free-form strings — all segments are arbitrary strings. Communities converge on naming through social consensus (e.g., "portland" not "pdx"), the same way they do today. No ISO codes, no standardized taxonomy, no gatekeeping.
- Used by services — MHR-Name scopes names by geographic scope, MHR-Pub supports
Scope(match)subscriptions, MHR-DHT uses scopes for content propagation boundaries, and the Social layer uses scopes for geographic and interest feeds
Geographic Scope Verification
Geographic scopes can be verified through bottom-up aggregation — see Identity Claims for the full verification protocol. In summary:
- Neighborhood level: Verified by RadioRangeProof — if you can hear a node's LoRa beacon, you're within physical range (~1–15 km)
- City level: Verified by aggregating multiple verified neighborhood claims within the city
- Region/Country level: Verified by aggregating verified city claims within the region
Interest scopes are never verified — anyone can declare interest in Pokemon. Verification only matters for geographic claims because they're prerequisites for geographic voting and local governance (see Voting).
Wire Format
Designed for constrained devices:
| Field | Size | Description |
|---|---|---|
scope_type | 1 byte | 0 = Geo, 1 = Topic |
segment_count | 1 byte | Number of path segments (max 8) |
segments | variable | Length-prefixed UTF-8 (1-byte length + content per segment) |
Maximum size per scope: 2 + 8 × 33 = 266 bytes. A node with 8 scopes uses at most ~2.1 KB for scope data.
Backward Compatibility
The community_label field is retained for backward compatibility. New nodes populate both:
Migration:
community_label: "portland-mesh"
→ scopes: [Geo("portland")]
Old nodes: read community_label, ignore scopes
New nodes: read scopes, fall back to community_label if scopes is empty
Geographic vs. Interest: Two Dimensions of Community
| Geographic Scopes | Interest Scopes | |
|---|---|---|
| Density | Dense — most people are somewhere | Sparse — niche interests span the globe |
| Propagation | Bottom-up through physical proximity | Wide, across geography |
| Verification | RadioRangeProof + peer vouches | None needed (self-declared) |
| Content cost | Cheap locally, expensive globally | Depends on relay distance |
| Voting | Enables geographic voting | No voting implications |
| Examples | Geo("north-america", "us", "oregon", "portland") | Topic("gaming", "pokemon") |
A single post can have both a geographic and interest scope. A post tagged Geo("portland") + Topic("gaming", "pokemon") appears in both the Portland local feed and the global Pokemon feed. Intersection queries ("Portland Pokemon") are resolved client-side by filtering on both scopes.
Comparison: Zones vs. Trust Neighborhoods
| Aspect | Explicit Zones (old) | Trust Neighborhoods (current) |
|---|---|---|
| Creation | Someone creates a zone | Emerges from mutual trust |
| Joining | Request + approval | Mark someone as trusted |
| Governance | Admin keys, voting | None needed |
| Boundaries | Hard, declared | Soft, overlapping |
| Free communication | Within zone boundary | Between any trusted peers |
| Naming | alice@zone-name | alice@geo:portland (hierarchical scopes) |
| Sybil resistance | Admission policy | Trust is social and economic (you absorb their debts) |
| UX complexity | Create, join, configure | Add contacts |
Sybil Mitigation
The trust graph provides natural Sybil resistance:
- Trust has economic cost: Vouching for a node means absorbing its potential debts. Sybil identities with no real relationships get no credit.
- Rate limiting: Even if a malicious node gains one trust relationship, transitive credit is capped at 10% per hop.
- Reputation: A node's usefulness as a relay/service provider is what earns trust over time. Creating many identities dilutes reputation rather than concentrating it.
- Local detection: A node's trust graph is visible to its peers. A node trusting an unusual number of new, unproven identities is itself suspicious.
Real-World Parallels
| Real World | Mehr Trust |
|---|---|
| Talk to your neighbor for free | Free relay between trusted peers |
| Lend money to a friend | Transitive credit via trust |
| Wouldn't lend to a stranger | No credit without trust chain |
| Communities aren't corporations | Neighborhoods have no admin |
| You belong to multiple groups | Trust graph is continuous, not partitioned |
| Reputation builds over time | Trust earned through reliable service |