Skip to content

Decentralized key revocation pipelines for admin roster sets#509

Merged
Sadeequ merged 4 commits into
StellarFlow-Network:mainfrom
Aonlike:Decentralized-Key-Revocation-Pipelines-for-Admin-Roster-Sets
Jun 27, 2026
Merged

Decentralized key revocation pipelines for admin roster sets#509
Sadeequ merged 4 commits into
StellarFlow-Network:mainfrom
Aonlike:Decentralized-Key-Revocation-Pipelines-for-Admin-Roster-Sets

Conversation

@Aonlike

@Aonlike Aonlike commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Closes #409

This pull request introduces an emergency key revocation mechanism that allows a predefined multi-signature coordinator group to immediately revoke a compromised administrative or coordinator hot-wallet key without requiring a full contract upgrade.
Once the required voting threshold is reached:
The compromised address is immediately marked as revoked in REVOKED_SIGNER_KEY persistent storage.
The address is stripped from the active signer set.
If the target was admin, admin rights are transferred to the replacement address.
Storage is updated atomically in the same transaction as the deciding vote.
All future authorization checks reject the revoked account from signing transactions or modifying contract configuration.

Motivation
Previously, if an administrator or coordinator hot wallet became compromised, the only mitigation was deploying an upgraded contract.
This introduced several risks:
Delayed response to security incidents.
Opportunity for attackers to execute malicious administrative actions during the upgrade window.
Operational downtime during the upgrade process.
Manual intervention requiring broad team coordination.
This implementation enables secure, decentralized on-chain emergency revocation through coordinator consensus with no contract upgrade required.

Technical Design
New Storage Keys

pub(crate) const REVOKED_SIGNER_KEY: Symbol = symbol_short!("REVOKED");
pub(crate) const EMERGENCY_REVOCATION_KEY: Symbol = symbol_short!("EMERREV");

The following storage entries are introduced:
REVOKED_SIGNER_KEY stores a Map<Address, ()> of all permanently revoked addresses.
EMERGENCY_REVOCATION_KEY stores the active EmergencyRevocationProposal while voting is in progress.

EmergencyRevocationProposal

pub struct EmergencyRevocationProposal {
    pub target: Address,
    pub replacement: Address,
    pub proposer: Address,
    pub proposed_at: u64,
    pub votes: Map<Address, ()>,
}

This structure tracks:
the compromised address to be revoked
the replacement address to be promoted
the coordinator who opened the proposal
the ledger timestamp at proposal time
the set of addresses that have already voted

New Functions
propose_emergency_revocation()

pub fn propose_emergency_revocation(
    env: &Env,
    proposer: Address,
    target: Address,
    replacement: Address,
) -> Result<(), ContractError>

Responsibilities:
Authenticate the proposer.
Verify proposer is a registered signer or admin.
Reject the compromised key from opening its own proposal.
Enforce only one active proposal at a time.
Record the proposer's opening vote automatically.
Store the proposal under EMERGENCY_REVOCATION_KEY.

vote_emergency_revocation()

pub fn vote_emergency_revocation(
    env: &Env,
    voter: Address,
    sig_expires_at: u64,
) -> Result<(), ContractError>

Responsibilities:
Reject stale signatures.
Authenticate the voter.
Verify voter is a registered signer or admin.
Prevent the target from voting on its own revocation.
Prevent duplicate votes.
Check whether the majority threshold (n/2 + 1) has been reached.
Execute revocation atomically once threshold is satisfied.

assert_not_revoked()

pub fn assert_not_revoked(
    env: &Env,
    addr: &Address,
) -> Result<(), ContractError>

Enforcing guard called at the top of every sensitive function. Returns RevokedAddress immediately if the caller has been revoked.

is_revoked()

pub fn is_revoked(
    env: &Env,
    addr: &Address,
) -> bool

Returns true if the supplied address has been stamped as revoked in REVOKED_SIGNER_KEY storage.

Authorization Changes
Every privileged operation in src/lib.rs now calls assert_not_revoked() before proceeding:

admin::assert_not_revoked(&env, &caller)?;

The check is applied to:
Function Protection
propose_upgrade Revoked key cannot propose upgrades
execute_upgrade Revoked key cannot execute upgrades
cancel_upgrade Revoked key cannot cancel upgrades
set_value Revoked key cannot modify state
register_signer Revoked key cannot add signers
remove_signer Revoked key cannot remove signers
stake_and_register Revoked node cannot re-stake
stake_and_register_for_feed Revoked node cannot register feeds
set_heartbeat_interval Revoked key cannot change intervals
upsert_node_profile Revoked key cannot modify node profiles
set_staking_tier_config Revoked key cannot change tier config
set_asset_feed_metrics Revoked key cannot update feed metrics
update_validator_profile Revoked node cannot update profile
vote_revocation Revoked key cannot participate in governance

Multi-Signature Workflow

Coordinator A
      │
      ▼
propose_emergency_revocation(target, replacement)
      │
      ▼
Proposal stored + proposer vote counted automatically
      │
      ▼
Coordinator B calls vote_emergency_revocation()
      │
      ▼
Majority threshold reached (n/2 + 1)
      │
      ▼
REVOKED_SIGNER_KEY updated immediately
      │
      ▼
Target removed from signer set
      │
      ▼
Replacement promoted into signer set
      │
      ▼
Admin rights transferred if target was admin
      │
      ▼
Proposal cleared from storage
      │
      ▼
Target permanently loses all permissions

Storage Updates
Immediately after the voting threshold is reached:

// 1. Stamp target as revoked
revoked.set(proposal.target.clone(), ());
env.storage().instance().set(&REVOKED_SIGNER_KEY, &revoked);

// 2. Remove from active signer set
signers.remove(proposal.target.clone());
env.storage().instance().set(&SIGNERS_KEY, &signers);

// 3. Transfer admin rights if needed
if contract_data.admin == proposal.target {
    contract_data.admin = proposal.replacement.clone();
    env.storage().instance().set(&DATA_KEY, &contract_data);
}

No contract upgrade is required at any point.

Security Improvements
Immediate Permission Removal
Permissions are revoked during the same transaction that satisfies the voting threshold. There is no intermediate state where the compromised key retains any access.

Self-Revocation Prevented
The compromised key cannot open its own revocation proposal and cannot vote on its own revocation:

if proposer == target {
    return Err(ContractError::Unauthorized);
}
if voter == proposal.target {
    return Err(ContractError::Unauthorized);
}

Duplicate Vote Prevention
Each coordinator can vote only once per proposal. Duplicate votes are rejected with AlreadyVoted.

Signature Expiry Enforcement
Votes with stale signatures are rejected immediately:

if env.ledger().timestamp() > sig_expires_at {
    return Err(ContractError::SignatureExpired);
}

Atomic Execution
Revocation and all storage updates occur atomically within a single transaction.

New Error Variants

RevokedAddress = 23,
EmergencyRevocationAlreadyActive = 24,
NoActiveEmergencyRevocation = 25,
TransferAlreadyPending = 26,
NoPendingOwner = 27,

Files Modified

src/admin.rs
src/lib.rs
src/test.rs

Testing
The following test cases were added to src/test.rs:
Successful Proposal
Proposal opens correctly and proposer vote is counted automatically.
Threshold Execution
Target is blocked and proposal is cleared once majority is reached.
Revoked Address Blocking
Revoked address gets RevokedAddress on stake_and_register and register_signer.
Revoked Admin Upgrade Prevention
Revoked admin key gets RevokedAddress on propose_upgrade.
Self-Revocation Prevention
Target voting on its own revocation returns Unauthorized.
Duplicate Vote Prevention
Second vote from the same address returns AlreadyVoted.
Single Active Proposal Enforcement
Opening a second proposal while one is active returns EmergencyRevocationAlreadyActive.
Expired Signature Rejection
Stale sig_expires_at returns SignatureExpired.
No Active Proposal Error
Voting with no active proposal returns NoActiveEmergencyRevocation.
Replacement Promotion
Replacement address is admitted into the signer set after revocation completes.

Checklist
[x] Emergency key revocation implemented inside src/admin.rs
[x] Multi-signature coordinator voting with majority threshold
[x] Immediate storage flag update on successful vote
[x] Revoked key blocked from signing
[x] Revoked key blocked from modifying configurations
[x] Self-revocation prevented
[x] Duplicate vote prevention
[x] Signature expiry enforcement
[x] Atomic execution — no intermediate state
[x] No contract upgrade required
[x] All new code covered by tests

Result
This implementation introduces a secure, decentralized emergency key revocation mechanism for the StellarFlow Network.
Once the configured multi-signature coordinator threshold is satisfied, the compromised administrative or coordinator account is immediately revoked, its permissions are removed from storage, and all future attempts to sign transactions or modify contract state are rejected.
This significantly strengthens the network's resilience against compromised administrative hot wallets while eliminating the need for an emergency contract upgrade.

Aonlike added 2 commits June 26, 2026 01:23
…roup

Introduces a two-phase multi-sig revocation flow in src/admin.rs to
strip permissions from a compromised hot-wallet key without requiring
a full contract upgrade.

- Add propose_emergency_revocation() and vote_emergency_revocation()
  with majority threshold enforcement (n/2 + 1)
- On threshold, instantly write REVOKED_SIGNER_KEY storage flag and
  strip target from signer set
- Add assert_not_revoked() guard to all mutating functions in lib.rs
- Add 10 tests covering the full revocation flow
@drips-wave

drips-wave Bot commented Jun 26, 2026

Copy link
Copy Markdown

@Aonlike Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Sadeequ

Sadeequ commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Alot of heavy text, yeah?

@Sadeequ Sadeequ merged commit 021d21b into StellarFlow-Network:main Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🛡️ Identity-Access | Decentralized Key Revocation Pipelines for Admin Roster Sets

2 participants