← Back to Proposals
Role Separation Decisions — Addendum v1 ADDENDUM
Decisions on the four open questions from
Role Separation & Warden-Based Auth v1,
plus new design for canary-phrase duress lockdown and permissionless Warden admission. — April 18, 2026
Summary: All four open questions are resolved. Manager keys are independent. Sentry-toggle is an extension-level capability for any signed-in wallet. Admin plane gets a two-factor login plus a canary phrase that silently locks the admin wallet under duress. Permissionless MainNet Warden admission is a phased pipeline — DevNet tenure + community engagement + operator vote — that ships as a dedicated follow-on proposal after v1.2.
1. Decisions on Open Questions #1 & #2
Open #1 — Manager wallet derivation ACCEPTED
Decision: Independent keypairs.
Each operator's manager wallet is a freshly-generated keypair, fully independent of the operator wallet. The operator signs a one-time on-chain binding message saying "pubkey M is my manager wallet for node N". Rotating a manager wallet is a new binding signature by the operator. Compromise of the manager wallet does not compromise the operator wallet.
Open #2 — Sentry toggle ACCEPTED
Decision: Sentry mode is an extension-level capability, not a user-plane feature.
The Sentry toggle lives on whichever wallet has signed into the extension. It is not an exception to the wallet-class separation rule — it is a property of the extension itself. Any wallet (user, Warden-operator, Bastion-operator, even admin) can flip the toggle. Re-framing §3 of the base proposal to remove the "blessed exception" language in favor of this cleaner description.
2. Canary Phrase — Duress Lockdown Design
Decision on Open #3: Yes to secondary admin password and canary phrase for duress scenarios. Here is the full design.
2.1 Registration
- Each admin registers two distinct phrases: a
real password and a canary phrase.
- Both stored as separate argon2id hashes in
admin_credentials.
- Registration-time validation rejects canaries that are within edit-distance 3 of the real password, eliminating "I accidentally typed the canary" incidents.
- Canary phrase is displayed once during registration, with a warning:
"Memorize this. If you are ever coerced into logging in, type this instead of your real password."
- Minimum canary length enforced at 16 characters; recommend a passphrase rather than a password.
2.2 Runtime flow
Admin wallet signs challenge
│
▼
Prompt for admin password
│
┌─────────┼─────────────────────┐
▼ ▼ ▼
matches matches no match
real canary │
│ │ ▼
│ │ rate-limited fail
│ │ (5 attempts → 1h cooldown)
▼ ▼
normal DURESS SESSION
session │
┌────┼────────────────────────────────────────┐
▼ ▼ ▼
UI unchanged all writes silently no-op admin wallet flagged
(attacker sees (attacker sees 'success') LOCKED in AdminRegistry
normal plane) reads return frozen data broadcast silent alert
2.3 The duress session behaves like this
- UI is indistinguishable from a real admin session. The attacker sees treasury balances, network stats, the usual navigation.
- All write operations silently no-op — they return HTTP 200 with fabricated success messages. Nothing actually mutates.
- All read operations return sanitized or frozen-in-time data. Sensitive values (bond amounts, private node keys, operator contact info) are replaced with plausible-looking placeholders.
- Every page visit and every attempted write is logged to
duress_audit with timestamp, IP, user-agent, wallet, pathname. Evidence for post-incident review.
2.4 Alert channel
On canary match, a silent alert is triggered out-of-band:
- Discord DM (via the existing
discord-bot service) to the other admin and an operator security channel.
- Email via the
alerts-service.
- SMS via a pre-registered phone number (optional; requires Twilio or similar — design it now, wire it during implementation).
- Alert payload includes timestamp, source IP, wallet, user-agent, and which pages were visited during the duress session.
2.5 Unlock
- The other admin (the one not under duress) must sign an on-chain
lift_duress(wallet) instruction on AdminRegistry.
- Requires: the other admin's wallet signature + their admin password (not canary) + confirming they spoke to the duressed admin via an out-of-band channel.
- 24-hour cooldown before the lifted admin can issue new write operations. This creates a forced "sleep on it, re-verify" window.
2.6 Edge case: both admins coerced simultaneously
The nuclear state. Both admins enter canaries; both are locked; no one can unlock. Mitigation: a time-locked recovery wallet — a third key held in cold storage (hardware wallet in a physical safe) that, after a 7-day waiting period and three on-chain confirmations from the recovery wallet itself, can unilaterally lift all duress flags. Not v1.2-critical. Lands in v2.0 alongside the on-chain programs.
2.7 On-chain additions
The AdminRegistry PDA gets a duress_state field per admin entry:
AdminEntry {
wallet: Pubkey,
added_at: i64,
duress_locked: bool, // NEW
duress_since: Option<i64>, // NEW
duress_reason: Option<String>, // NEW (free-form reason from unlock record)
}
New instructions:
flag_duress(wallet) — signed by the Warden that detected the canary match. Sets duress_locked = true.
lift_duress(wallet, reason) — signed by another admin + admin-password check. Clears the flag; records the reason.
time_locked_recovery_lift(wallet) — signed by the recovery wallet; requires 7-day on-chain delay; clears all duress flags. v2.0.
3. Permissionless MainNet Warden Admission
Decision on Open #4: Yes, and the model is stronger than the base proposal described. DevNet tenure + community engagement + operator vote. Detailed design follows; scoping note at the bottom.
Core principle: Operators contribute to the network before they benefit from the network. Running a DevNet node, engaging with other operators, and earning peer trust are prerequisites for earning a MainNet Warden slot.
3.1 The pipeline
Anyone (low bar) Existing operators approve Admin multisig
│ │ │
▼ ▼ ▼
┌──────────────┐ 30+ days ┌────────────────────┐ 7–14 day ┌─────────────┐
│ DevNet │ uptime + │ MainNet │ operator │ MainNet │
│ Warden │ community │ Warden │ vote │ Warden │
│ (apply + │ ─────────────▶│ Application │ ─────────────▶│ Active │
│ zero bond │ engagement │ (DevNet tenure │ quorum + │ │
│ on DevNet) │ │ proof + bond │ threshold │ admin veto │
│ │ │ commitment) │ │ retained │
└──────────────┘ └────────────────────┘ └─────────────┘
3.2 What gets tracked for "community engagement"
Most DePIN projects score only technical performance. Adding social contribution as a gate is the novel part, and it aligns well with the Bastion-network trust model.
| Signal | Source | On/off-chain | Weight (illustrative) |
| DevNet uptime % (30d) | WardenRegistry heartbeat history | On-chain | High |
| DevNet slashing incidents | WardenRegistry slash events | On-chain | High (negative) |
| Forum / Discord presence | discord-bot tracks post count, response rate, hours-active | Off-chain (published read-only) | Medium |
| Peer vouches | Existing operators sign a vouch(candidate) instruction | On-chain | High |
| Technical contribution | GitHub PRs, ops docs, community support | Off-chain (admin-weighted) | Low / bonus |
3.3 Voting mechanics (rough shape)
- Quorum: 60% of existing MainNet Wardens must participate for the vote to count.
- Threshold: 2/3 of votes cast must be Yes.
- Admin veto: retained in the early network (≤20 MainNet Wardens). Phased out by governance proposal as the operator pool grows.
- Votes are on-chain with a short reason string (max 280 chars). Public, transparent, auditable.
- Failed applications can re-apply after 30 days with updated engagement evidence.
3.4 OperatorProfile PDA
A new on-chain object tracks operator identity and peer relationships:
OperatorProfile {
operator_wallet: Pubkey,
handle: String, // Self-chosen display name
devnet_nodes: Vec<Pubkey>, // Nodes this operator runs on DevNet
mainnet_nodes: Vec<Pubkey>, // Nodes on MainNet
vouches_received: Vec<Vouch>, // Who vouches for this operator
vouches_given: Vec<Vouch>, // Who this operator vouches for
first_seen: i64,
last_active: i64, // Updated by any on-chain op they sign
metadata_uri: Option<String>, // Off-chain profile (bio, socials)
}
Vouch {
voucher: Pubkey,
vouchee: Pubkey,
reason: String, // Max 280 chars
signed_at: i64,
revoked: bool,
}
3.5 Scope & sequencing
Separate proposal, separate timeline. Implementing the voting mechanics, vouching UI, discord-bot engagement tracking, and OperatorProfile PDA is a meaningful chunk of work. It should
not block v1.2 (role separation). Suggested sequencing:
- v1.2 — role separation ships. Admin-approved Warden admission, both DevNet and MainNet.
- v2.0 — on-chain programs ship. Admin-approved admission continues.
- v2.1 / MainNet launch — MainNet goes live with admin-approved admission.
- v2.2+ — dedicated follow-on proposal implements the permissionless voted admission. Prerequisite: ≥3 MainNet Wardens live and ≥60 days of operator-community history.
The base proposal's §9 will be updated: "permissionless in v2" becomes "admin-approved in v1.2 and v2.0; permissionless operator-voted admission in a dedicated follow-on proposal (v2.2+)."
4. Domain Name Flexibility
The concern: "I do NOT want to use the current domain name for the WardenRegistry. If this is permanent from the first step, we cannot do it until the domains are secured."
Non-blocking. The architecture already handles this cleanly.
WardenEntry.public_url is mutable via update_url(manager_wallet). Changing a Warden's domain is a normal on-chain operation, not a program redeploy.
- For DevNet (where we iterate freely), we seed Warden #1 with whatever URL is most convenient — placeholder, temporary subdomain, even
localhost:8443 for local dev. We change it freely with signed update_url ops.
- For MainNet launch, the permanent domain(s) are secured first, then Warden #1 is registered with the real URL from the start.
- Clients cache the URL and invalidate on registry refresh (≤ 5 minutes), so URL changes propagate naturally.
4.1 Small addition
I'll include a convenience url_history field (last N URLs) stored in the entry or emitted as program logs, so clients can display "this Warden was previously at X" — helpful during operator migrations and for debugging cache invalidation issues.
5. Proposal Update Plan
On your approval of this addendum, I will revise the base proposal (proposal-role-separation-v1.html) with the following edits:
| Section | Change |
| §3 Wallet classes | Reframe Sentry as extension-toggle capability, not an exception to wallet-class separation. |
| §6 AdminRegistry | Add duress_state fields. Add flag_duress, lift_duress, time_locked_recovery_lift instructions. Reference §2 of this addendum for full canary design. |
| §6 WardenRegistry | Explicit note that public_url is mutable. Add optional url_history. |
| §7 Impersonation defenses | Add "Duress alert channel" subsection describing discord-bot + alerts-service + out-of-band notification. |
| §9 Tracks | Update release plan: remove permissionless admission from v2; defer to v2.2+ follow-on proposal. Keep v1.2 + v2.0 + v2.1 as planned. |
| §10 Open decisions | Mark #1, #2, #3, #4 resolved. Point to this addendum for details. |
| §11 Risks | Add "both admins under duress simultaneously" with time-locked recovery mitigation. |
| §14 Follow-on work (NEW) | Scope: (a) permissionless operator-voted admission proposal, (b) time-locked recovery wallet. |
6. Resolutions to Questions A and B
Both questions resolved. Incorporating the answers into the final design below.
6.1 Canary lockdown scope RESOLVED
Decision: Admin plane fully locked, and every other plane that wallet has access to shows a warning requiring network-acknowledgment before continuing.
Neither of my two earlier options was right. The correct shape is a two-tier response:
| Plane | Behavior under duress flag |
| Admin |
Fully locked. Writes silently no-op; reads sanitized. (As originally designed in §2 of this addendum.) |
| Warden / Bastion / Sentry |
Prominent red banner at the top of every page: "This wallet is currently flagged as duress-locked on the network. Your operations will be broadcast-acknowledged on-chain and visible to every other Warden. Do you want to continue?" User must sign a duress_ack message with their wallet to proceed for the session. No operational blocking beyond that. |
| User / Vault |
Same warning banner + acknowledgment requirement. User can continue accessing their vault but with full network visibility. |
What "network acknowledge" means
On click of the acknowledgment button in a warned plane:
- The wallet signs a canonical message:
"SHARDKEEP_DURESS_ACK: plane=<plane> ts=<timestamp> session=<session_id>"
- The signature is posted to an on-chain audit log (or, for frequent-access planes like User, buffered and batched every N minutes to avoid gas thrash).
- Every other Warden sees the ack via registry sync and mirrors it to local audit logs.
- The banner persists throughout the session — "I acknowledged" doesn't hide the fact that the wallet is under duress.
- Acks are visible in the admin plane for the other (non-duressed) admin's investigation.
Why this design: It hits the sweet spot the user asked for. Admin is the high-stakes surface, so it stays fully locked. But coercers might pivot to using the operator or user plane of the same wallet ("you're locked out of admin? fine, just log into your Warden and transfer the stake"). The warning banner + on-chain ack makes that pivot visible to the entire network. The duressed admin doesn't lose access to their own vault or operator functions, but every action is publicly witnessed.
On-chain additions (updates to §2.7 of this addendum)
New instruction on AdminRegistry:
record_duress_ack(wallet, plane, session_id, timestamp, signature)
— Appended to on-chain audit log
— Signed by the wallet itself (proves the user clicked acknowledge)
— Rate-limited: one ack per (wallet, plane, session_id) per hour
New read accessor for display:
get_duress_acks(wallet, since_timestamp)
— Returns list of plane + timestamp tuples
— Used by the non-duressed admin's investigation UI
6.2 DevNet bond & admission model RESOLVED
Decision: DevNet has a complete admin-gated admission model — stronger than any of my three original options.
The actual mechanism already in place (or planned):
- DevNet has its own DevNet-SHRD token.
- There is no faucet. The only way to obtain DevNet-SHRD is via admin-performed airdrop.
- To run a DevNet node, an operator must contact admins out-of-band (Discord, email, etc.) and request an airdrop to their operator wallet.
- Admins review the request, airdrop DevNet-SHRD, and approve the node registration.
- Only then can the operator bond and activate their DevNet Warden/Bastion/Sentry.
Why this is stronger than a free-faucet model:
- Anti-Sybil by design. Can't spin up 1,000 DevNet Wardens to farm reputation — each one needs an admin airdrop, and admins notice patterns.
- Community engagement is baked in from day zero. Every DevNet operator has, by definition, already had at least one human conversation with an admin. The "social contribution" signal I was going to track for MainNet admission is automatically present in the DevNet onboarding.
- Admins control the DevNet operator pool. Which means the voting body that later decides MainNet admissions is itself curated. The permissionless-MainNet vote doesn't have to cold-start against Sybil risk because the voters were all individually admin-vetted when they entered DevNet.
- Zero friction for the right operators. The airdrop is free. The ask is a conversation. Legitimate operators don't mind; bots can't complete the onboarding.
Implementation implications
- New admin instruction:
airdrop_devnet_tokens(operator_wallet, amount, reason_uri) — admin multisig signature, records the airdrop with reason.
- New tracking table (
shardkeep_devnet_airdrops): timestamp, admin who signed, operator wallet, amount, reason, planned node type. This is the substrate the later voted-admission proposal (v2.2+) reads from to surface "operator X was airdropped for what purpose, by which admin."
- DevNet and MainNet use the same on-chain programs, distinguished by cluster. DevNet-SHRD is a distinct token mint from MainNet SHRD.
- No DevNet faucet, ever. Admins are the sole source of DevNet-SHRD. This is a deliberate network gate, not an implementation stopgap.
Consequence for the voted-admission pipeline (v2.2+)
Because every DevNet operator has been admin-vetted at entry, the community-engagement portion of the voted admission is already partially answered. The voted-admission proposal simplifies to:
- "Has this DevNet operator maintained their DevNet node with good uptime for 30+ days?" — pure on-chain metric.
- "Have existing MainNet Wardens had productive interactions with this operator?" — subjective, weighted by peer vouches.
- The "is this operator a Sybil?" question is already resolved at the DevNet-airdrop gate.
This materially reduces the complexity of the v2.2+ admission proposal. I'll note this in its follow-on scope.
7. What Happens Next
All open questions resolved. Ready to integrate into the base proposal on your approval of this addendum.
- You review this revised addendum (§6.1 canary warning design, §6.2 DevNet admin-gate model).
- On approval, I revise
proposal-role-separation-v1.html per the edit plan in §5, incorporating:
- §3 — Sentry as extension-toggle (not exception)
- §6 AdminRegistry — duress fields + instructions +
record_duress_ack
- §6 WardenRegistry — mutable
public_url + url_history
- §7 Impersonation — add Duress Alert Channel subsection
- §9 Tracks — defer permissionless admission to v2.2+
- §10 Open decisions — mark all 4 resolved
- §11 Risks — both-admins-coerced with time-locked recovery
- New §14 — Follow-on work (voted admission, time-locked recovery wallet)
- New entry: DevNet admin-airdrop gate model as a first-class architectural feature
- I create the progress log at
/shardkeep/proposals/logs/v1.2.0-role-separation.log.
- On final approval of the integrated proposal, Track 1 implementation begins (off-chain role plumbing + wallet class distinctions + duress-ack helper).
Proposed: April 18, 2026 · Status: All open questions resolved — awaiting approval to revise base proposal