← Back to Proposals

On-Chain Shard Map v2 PROPOSAL v2

Implementation plan, codebase impact assessment, and blind rotation via Solana program · April 2026

What changed from v1: This version adds a full codebase impact assessment, confirms the existing Anchor program needs ZERO changes, details the two rotation strategies (Option A: client-initiated, Option D: smart-contract blind swap), and provides a parallel development plan for testing Option D on DevNet alongside ongoing feature development.

1. Codebase Impact Assessment

Complete audit results

Every file in the ShardKeep/Citadel codebase was audited for impact. The on-chain shard map architecture affects only the shard coordination layer — the vault program, wallet flows, and browser extension UI are untouched.

ComponentFile(s)Current StateImpactEffort
Anchor Program (Rust) shardkeep_vault/lib.rs Single-account vault, 333 lines, 4 instructions NONE 0 hrs
Solana Bridge CLI solana-bridge.js Vault CRUD client, 592 lines NONE 0 hrs
Vault CRUD APIs solana-{store,retrieve,list,delete}.php HTTP proxy to solana-bridge NONE 0 hrs
Wallet Connect wallet-connect.js Wallet Standard auth, 232 lines NONE 0 hrs
ShardService.php includes/ShardService.php DB-backed shard location, 439 lines MAJOR 20-30 hrs
Shards API api/shards.php Store/retrieve/delete handlers MAJOR 10-15 hrs
Database Schema migrations/002_shards.sql citadel_shards table with node_id MAJOR 5-8 hrs
WSS Coordinator Python WSS service Polls DB for pending shards MODERATE 10-15 hrs
Extension storage.js storage.js Tier 2 shard stub (not implemented) MINOR 5-8 hrs
New Dependencies package.json @coral-xyz/anchor only MODERATE 2-4 hrs
Blind Swap Program New Solana program Does not exist yet NEW 80-120 hrs
Option A total (client-initiated rotation): 52-80 hours development + 20-30 hours testing = ~2-3 weeks
Option D total (blind swap program): Additional 80-120 hours = ~4-6 weeks on DevNet
Combined (parallel tracks): Option A ships first, Option D developed on DevNet simultaneously

What does NOT change (confirmed by audit)

2. Option A — Client-Initiated Rotation SHIP FIRST

How it works

Rotation happens only when the user opens the extension. Since password manager users open their extension daily (for auto-fill), this is a natural trigger.

User opens ShardKeep extension
Extension signs auth challenge with wallet
Reads user's shard map cNFTs from Solana (FREE — reads cost nothing)
Checks: "last rotation was N days ago, tier = Guardian (weekly)"

If rotation due:
Decrypts shard map locally
Retrieves k shards from current nodes (by shard hash, nodes don't know why)
Re-splits with fresh Shamir randomness (new shard set)
Server selects new target nodes (random, no overlap with previous)
Distributes new shards via WSS
Mints updated cNFT with new shard map (old leaf burned)
Server purges all shard-to-node data from memory
Old shards purged from previous nodes

// Server sees shard locations ONLY during the rotation window (~5-10 seconds)
// After cNFT mint + purge, server knows nothing again

Rotation schedule by tier:

TierRotation FrequencyTriggerCost per Entry
Shield (Pawn)None (manual only)User-initiated$0.001
Guardian (Knight)WeeklyExtension auto-check on unlock$0.001 × 52 = $0.052/year
Sentinel (Bishop/Rook)DailyExtension auto-check on unlock$0.001 × 365 = $0.365/year
Fortress (Queen)DailyExtension auto-check on unlock$0.001 × 365 = $0.365/year

What if the user doesn't open the extension?

Security during ephemeral exposure: The server sees shard locations for ~5-10 seconds during redistribution. This is unavoidable (someone must coordinate the transfer). Mitigations:
• WSS coordinator runs in-memory only (no disk writes of shard maps)
• Shard-to-node mapping held in process RAM, purged immediately after cNFT mint confirmation
• Audit log records that rotation occurred, but NOT the shard locations

3. Option D — Blind Rotation via Solana Program DEVNET R&D

The vision: zero-knowledge rotation

A new Solana program (separate from the vault program) that coordinates shard swaps between vault nodes without any single entity seeing the full shard map.

Rotation trigger (client-initiated, same as Option A)
Extension reads current cNFT shard map, decrypts locally
Extension generates N individual swap instructions:
    Instruction 1: "Node A: send blob 0xABC to Node B"
    Instruction 2: "Node C: send blob 0xDEF to Node D"
    Instruction 3: "Node E: send blob 0x123 to Node F"
Extension submits swap instructions to Solana program as a single transaction
On-chain program validates: signer is cNFT owner (wallet check)
Program emits individual swap events (each node sees ONLY its own instruction)
Nodes execute swaps peer-to-peer
Extension mints updated cNFT with new shard map

// No server is involved AT ALL
// Each node sees only: "take blob X, send to Node Y"
// No node knows the full map, who owns the blobs, or which entry they belong to
// The Solana program enforces that only the cNFT owner can issue swap instructions

What the blind swap program looks like (Anchor/Rust):

// Program: shardkeep_rotation (NEW, separate from shardkeep_vault)
// Network: DevNet for testing

#[program]
pub mod shardkeep_rotation {
    use anchor_lang::prelude::*;

    /// Issue a batch of shard swap instructions.
    /// Only the cNFT owner (verified via wallet signature) can call this.
    pub fn rotate_shards(ctx: Context<RotateShards>, swaps: Vec<SwapInstruction>) -> Result<()> {
        // Verify caller owns the shard map cNFT (Bubblegum ownership check)
        // Emit individual SwapEvent for each instruction
        // Nodes listen for their own events via WebSocket subscription
        for swap in swaps {
            emit!(SwapEvent {
                source_node: swap.source_node,
                target_node: swap.target_node,
                shard_hash: swap.shard_hash,
                nonce: swap.nonce,
            });
        }
        Ok(())
    }
}

#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct SwapInstruction {
    pub source_node: [u8; 32],  // Source node public key
    pub target_node: [u8; 32],  // Target node public key
    pub shard_hash: [u8; 32],   // Blob identifier
    pub nonce: u64,             // Replay protection
}

#[event]
pub struct SwapEvent {
    pub source_node: [u8; 32],
    pub target_node: [u8; 32],
    pub shard_hash: [u8; 32],
    pub nonce: u64,
}

What each party sees during blind rotation:

PartySeesDoes NOT See
User's extension Full shard map (decrypted locally) — (user sees everything, it's their data)
Solana program Individual swap instructions (source → target + hash) Which user, which entry, full map, entry contents
Source vault node "Send blob 0xABC to Node B" Who owns the blob, which entry, what other nodes hold sibling shards
Target vault node "Incoming blob 0xABC from Node A" Who owns the blob, which entry, what other nodes hold sibling shards
ShardKeep servers Nothing Everything — server is not involved in Option D rotation
RPC indexer (Helius) Encrypted cNFT metadata (can't decrypt) Shard locations, entry contents, user identity

Node-side: listening for swap events

Vault node subscribes to Solana WebSocket:
ws.onProgramEvent("shardkeep_rotation", filter: {source_node: MY_PUBKEY})

On SwapEvent received:
Look up blob by shard_hash in local storage
Open peer-to-peer connection to target_node
Transfer encrypted blob
Target node acks receipt
Source node deletes local copy
Both nodes emit on-chain confirmation (optional, for audit trail)

// Node never contacts ShardKeep servers during this flow
// Pure peer-to-peer, coordinated by on-chain events

4. Database Schema Changes

Migration strategy: shadow table approach

Keep the existing citadel_shards table operational during transition. Add cNFT references alongside, then phase out the node_id column once cNFT flow is validated.

-- Phase 1: Add cNFT columns to existing table
ALTER TABLE citadel_shards
    ADD COLUMN nft_mint VARCHAR(44) DEFAULT NULL AFTER hmac_key,
    ADD COLUMN nft_tree VARCHAR(44) DEFAULT NULL AFTER nft_mint,
    ADD COLUMN nft_leaf_index BIGINT DEFAULT NULL AFTER nft_tree,
    ADD COLUMN map_purged_at DATETIME DEFAULT NULL AFTER nft_leaf_index;

-- Phase 2: Shadow table for cNFT-first lookups
CREATE TABLE citadel_shard_maps (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    wallet_address VARCHAR(64) NOT NULL,
    entry_id VARCHAR(64) NOT NULL,
    nft_mint VARCHAR(44) NOT NULL,
    nft_tree VARCHAR(44) NOT NULL,
    leaf_index BIGINT NOT NULL,
    security_tier ENUM('shield','guardian','sentinel','fortress') NOT NULL,
    shard_count TINYINT NOT NULL,
    threshold TINYINT NOT NULL,
    encrypted_map VARBINARY(2048),  -- Encrypted shard map (backup, also on-chain)
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    rotated_at DATETIME DEFAULT NULL,
    burned_at DATETIME DEFAULT NULL,
    INDEX idx_wallet (wallet_address),
    INDEX idx_entry (entry_id),
    UNIQUE INDEX idx_mint (nft_mint)
) ENGINE=InnoDB;

-- Phase 3: After validation, drop node_id from citadel_shards
-- ALTER TABLE citadel_shards DROP COLUMN node_id;
-- (Only after Option A is fully operational and tested)
Critical: The node_id column is NOT dropped until Option A has been running successfully in production for at least 2 weeks. The shadow table allows both old and new flows to operate simultaneously during transition.

5. ShardService.php Changes (Detailed)

Three methods require modification

storeSecret() — lines 195-227

Current:
selectNodes() INSERT citadel_shards (node_id, shard_data, ...) WSS notify

Proposed:
selectNodes() INSERT citadel_shards (shard_data, no node_id yet)
WSS distributes shards to selected nodes
On ack: build shard map JSON {shard_hash → node_id, ...}
Encrypt map with user's wallet public key
Mint cNFT via Bubblegum with encrypted map as metadata
Store nft_mint in citadel_shard_maps
UPDATE citadel_shards SET node_id = NULL, map_purged_at = NOW()
// node_id purged from our database after cNFT is confirmed on-chain

retrieveSecret() — lines 302-378

Current:
SELECT node_id FROM citadel_shards WHERE entry_id = ? fetch from nodes reconstruct

Proposed:
Client reads cNFT metadata from Solana (extension-side, not server)
Client decrypts shard map locally
Client requests shards from nodes by hash
Client reconstructs locally
// Server is NOT involved in retrieval at all
// This method may become a thin status-check endpoint only

deleteEntry() — lines 384-392

Current:
UPDATE citadel_shards SET deleted_at = NOW()

Proposed:
Burn the cNFT (Bubblegum burn instruction)
Broadcast delete to vault nodes (by shard hash)
UPDATE citadel_shard_maps SET burned_at = NOW()
// On-chain proof that the shard map was intentionally destroyed

6. New Dependencies Required

Node.js packages (for shard map minting)

npm install @metaplex-foundation/mpl-bubblegum \
            @metaplex-foundation/mpl-token-metadata \
            @metaplex-foundation/umi \
            @metaplex-foundation/umi-bundle-defaults \
            @solana/spl-account-compression

Anchor (for Option D blind swap program)

# New program, separate workspace
anchor init shardkeep_rotation --solana-version 1.18
# Deploy to DevNet only during R&D phase

The existing shardkeep_vault Anchor workspace and program ID are completely untouched.

7. Parallel Development Plan

Two tracks, running simultaneously

Track A: Ship to Production

Client-initiated rotation with cNFT shard maps

  1. Week 1-2: Merkle tree creation on DevNet. Bubblegum mint/burn integration in ShardService.php. Schema migration (shadow table).
  2. Week 2-3: Extension storage.js: implement Tier 2 shard store/retrieve via cNFT. Rotation auto-check on unlock.
  3. Week 3-4: WSS coordinator update: in-memory-only shard maps, purge after cNFT confirmation. Integration testing.
  4. Week 4-5: DevNet end-to-end testing. Rotation cycle validation. Edge cases (offline user, node failure during rotation).
  5. Week 5-6: MainNet Merkle tree. Production deployment. Monitor for 2 weeks before dropping node_id column.

Track D: DevNet R&D

Blind swap Solana program

  1. Week 1-3: Design shardkeep_rotation program. Define SwapInstruction and SwapEvent schemas. Write Anchor program skeleton.
  2. Week 3-5: Implement swap instruction handler. Ownership verification (cNFT owner check via Bubblegum). Event emission.
  3. Week 5-7: Build node-side WebSocket listener. Peer-to-peer blob transfer protocol. Swap ack mechanism.
  4. Week 7-9: Integration test on DevNet: extension issues swap instructions → program emits events → nodes execute swaps → cNFT updated.
  5. Week 9-10: Performance testing. Transaction cost analysis. Gas optimization. Batch swap efficiency.
  6. Week 10+: Security audit preparation. MainNet deployment decision based on results.
Track A ships independently. Option D is pure R&D on DevNet — it does not block any production feature. If Option D testing succeeds, it becomes an optional upgrade that replaces the server-coordinated rotation in Track A with fully on-chain blind swaps. If Option D proves too complex or expensive, Track A remains the production architecture indefinitely.

8. Cost Summary

OperationOn-Chain CostAt $200/SOL
Merkle tree creation (one-time, DevNet free)5-20 SOL (MainNet)$1,000-4,000 one-time
Mint shard map cNFT (per entry stored)~0.000005 SOL$0.001
Update cNFT (rotation, per entry)~0.000005 SOL$0.001
Burn cNFT (entry deleted)~0.000005 SOL$0.001
Read cNFT metadata (every unlock)FREE$0.00
Blind swap instruction (Option D, per swap)~0.000012 SOL$0.0024
Annual cost per user (Guardian tier, 100 entries, weekly rotation):
Initial mint: 100 × $0.001 = $0.10
Weekly rotations: 100 entries × 52 weeks × $0.001 = $5.20
Reads: FREE
Total: $5.30/year (< 30 cents/month)

At 1M users (100M total entries):
One-time tree: $2,000
Annual mints + rotations: ~$530,000
vs. regular Solana accounts for same: ~$56,000,000
cNFTs save $55.47M annually at scale

9. The Competitive Moat

Option A makes ShardKeep the only password manager where the operator can't locate your shards.

Option D makes ShardKeep the only password manager where nobody can locate your shards — not even during rotation.

Combined, this is a cryptographic moat that no competitor can replicate without rebuilding their entire architecture from scratch.

Supersedes proposal-onchain-shard-map-v1. References: vault-security-v3, tokenomics-v3.1, addendum-revenue-streams-v1.
Anchor program 4hfvirYMHxW4nZSuTreWRQQD45Hfc4LKmUyy3hFYcZVP confirmed unaffected. All DevNet costs are free. MainNet Merkle tree is the only significant upfront cost ($1-4K one-time).