Decentralized Storage (IPFS)

Private IPFS network for MPC enclave storage, Vault ejection capabilities, and decentralized content addressing across Sonr's node infrastructure

Decentralized Storage with IPFS

Scope

This document covers Sonr's private IPFS network implementation for MPC share storage, Vault ejection, and content addressing. It includes network architecture, node integration, security models, and data management. This document does not cover public IPFS integration or low-level libp2p protocols—see the IPFS reference documentation for protocol details.

Audience

Infrastructure engineers operating Sonr nodes. Developers building applications with decentralized storage. System architects designing distributed systems. Prerequisites: Understanding of distributed storage concepts, basic IPFS knowledge, and familiarity with content-addressed storage.

Summary

Sonr operates a private IPFS network for Multi-Party Computation (MPC) share storage and Vault ejection. All node types—snrd (blockchain), hway (proxy), and motr (enclave)—automatically join this network. The system enables Vaults to operate independently of the blockchain while maintaining functionality. Data remains isolated from public IPFS through network-level security.

Network Architecture

Private Network Isolation

Sonr's IPFS network operates separately from public IPFS:

# Private network configuration
export LIBP2P_FORCE_PNET=1
export IPFS_SWARM_KEY="/key/swarm/psk/1.0.0/..."

# Ensures:
# - No public IPFS connections
# - Only authorized nodes join
# - Data remains within Sonr
# - Network-level access control

Node Integration

All Sonr infrastructure components connect to private IPFS:

MPC Share Storage

Distributed Key Management

MPC shares distribute across multiple nodes:

interface MPCShareStorage {
  shareID: string;
  threshold: number; // Shares needed to reconstruct
  totalShares: number; // Total shares created
  encryptedShares: {
    nodeID: string; // Node storing this share
    ipfsCID: string; // Content identifier
    encryptedData: string;
    verificationHash: string;
  }[];
}

Share Resolution

Vaults retrieve MPC shares dynamically:

func (v *Vault) resolveMPCShares(ctx context.Context, shareID string) (*MPCShares, error) {
    // Get share metadata
    metadata, err := v.ipfs.Get(shareID)
    if err != nil {
        return nil, err
    }

    var shares MPCShareStorage
    json.Unmarshal(metadata, &shares)

    // Collect threshold shares
    collected := []EncryptedShare{}
    for _, share := range shares.EncryptedShares {
        // Verify and retrieve share
        if exists, _ := v.ipfs.Exists(share.IPFSCID); exists {
            data, _ := v.ipfs.Get(share.IPFSCID)
            collected = append(collected, EncryptedShare{
                NodeID: share.NodeID,
                Data:   data,
            })
        }

        // Stop when threshold reached
        if len(collected) >= shares.Threshold {
            break
        }
    }

    return &MPCShares{
        ShareID: shareID,
        Shares:  collected,
    }, nil
}

Vault Ejection

Blockchain Independence

Vaults can eject from the blockchain while maintaining functionality:

class EjectableVault {
  async ejectFromBlockchain(): Promise<EjectedVault> {
    // 1. Export state to IPFS
    const stateCID = await this.ipfs.add(await this.exportState());

    // 2. Verify MPC shares available
    await this.verifyMPCShareAvailability();

    // 3. Create ejected configuration
    const ejectedConfig = {
      type: "ejected",
      ipfsConfig: {
        network: "sonr-private",
        stateCID: stateCID,
        mpcShareID: this.mpcShares.shareID,
      },
      capabilities: {
        payments: true,
        crossChain: true,
        storage: true,
        computation: true,
      },
      blockchainConnection: null,
    };

    // 4. Store ejection manifest
    const manifestCID = await this.ipfs.add(ejectedConfig);

    return new EjectedVault(manifestCID, ejectedConfig);
  }
}

Ejected Capabilities

Ejected Vaults maintain full operations:

class EjectedVault {
  // Payments via cached channels
  async processPayment(payment: PaymentRequest) {
    const channels = await this.loadPaymentChannels();
    return this.executePaymentViaChannel(payment, channels);
  }

  // Cross-chain via cached IBC config
  async crossChainTransfer(transfer: IBCTransfer) {
    const ibcConfig = await this.loadIBCConfiguration();
    return this.executeIBCTransfer(transfer, ibcConfig);
  }

  // Storage continues via IPFS
  async storeData(data: any): Promise<string> {
    return this.ipfs.add(JSON.stringify(data));
  }

  // Local WASM computation
  async executeComputation(wasmModule: Uint8Array, input: any) {
    const instance = await WebAssembly.instantiate(wasmModule);
    return instance.exports.compute(input);
  }
}

Node Bootstrap

Automatic Network Join

Every node joins IPFS during initialization:

// snrd node bootstrap
func (n *SnrdNode) Bootstrap(ctx context.Context) error {
    // Initialize blockchain
    if err := n.initBlockchainNode(); err != nil {
        return err
    }

    // Join private IPFS
    ipfsNode, err := n.initIPFSNode()
    if err != nil {
        return err
    }

    // Connect to bootstrap peers
    for _, peer := range n.config.IPFSBootstrapPeers {
        ipfsNode.Connect(ctx, peer)
    }

    // Register as MPC storage node
    return n.registerMPCStorageNode()
}

// hway node bootstrap
func (h *HwayNode) Bootstrap(ctx context.Context) error {
    // Initialize HTTP server
    if err := h.initHTTPServer(); err != nil {
        return err
    }

    // Join IPFS for caching
    if err := h.initIPFSNode(); err != nil {
        return err
    }

    return h.initVaultCache()
}

// motr node bootstrap
func (m *MotrNode) Bootstrap(ctx context.Context) error {
    // Initialize WASM runtime
    if err := m.initWASMRuntime(); err != nil {
        return err
    }

    // Join IPFS for storage
    if err := m.initIPFSNode(); err != nil {
        return err
    }

    return m.registerMPCComputeNode()
}

Data Management

Content Addressing

IPFS provides automatic deduplication:

func StoreVaultData(data []byte, ipfs ipfs.Client) (string, error) {
    // Content-addressed storage
    cid, err := ipfs.Add(data)
    if err != nil {
        return "", err
    }

    // Pin important data
    if isImportantData(data) {
        ipfs.Pin(cid, generatePinName(data))
    }

    return cid, nil
}

func RetrieveVaultData(cid string, ipfs ipfs.Client) ([]byte, error) {
    // Check local availability
    if exists, _ := ipfs.Exists(cid); !exists {
        return nil, fmt.Errorf("data not available: %s", cid)
    }

    return ipfs.Get(cid)
}

Vault Configuration Storage

Configurations are versioned and stored:

interface VaultConfigIPFS {
  version: string;
  created: number;
  updated: number;
  owner: string; // DID
  mpcConfig: {
    threshold: number;
    shareNodes: string[];
  };
  capabilities: {
    payments: boolean;
    crossChain: boolean;
    storage: boolean;
    computation: boolean;
  };
}

async function storeVaultConfig(
  config: VaultConfigIPFS,
  ipfs: IPFSClient,
): Promise<string> {
  // Add metadata
  const configWithMeta = {
    ...config,
    updated: Date.now(),
    version: generateVersion(config),
  };

  // Store in IPFS
  const cid = await ipfs.add(JSON.stringify(configWithMeta));

  // Pin for persistence
  await ipfs.pin(cid, `vault-config-${config.owner}`);

  // Update mutable reference
  await ipfs.name.publish(cid, { key: `vault-${config.owner}` });

  return cid;
}

Security Model

Network Access Control

Private network implements multiple security layers:

# Swarm key for isolation
/key/swarm/psk/1.0.0/
/base16/
[network_key_hex]

# Peer authentication
IPFS_AUTH_METHOD="signature"
IPFS_PEER_ALLOWLIST="/path/to/allowed_peers.json"

# TLS encryption
LIBP2P_TLS=true
LIBP2P_NOISE=true

Data Encryption

Sensitive data encrypts before storage:

class SecureIPFSStorage {
  async storeEncrypted(data: any, policy: AccessPolicy): Promise<string> {
    // Serialize data
    const serialized = JSON.stringify(data);

    // Encrypt with Vault key
    const encrypted = await this.encrypt(serialized);

    // Create metadata
    const metadata = {
      encrypted: true,
      accessPolicy: policy,
      timestamp: Date.now(),
      algorithm: "AES-256-GCM",
    };

    // Combine and store
    const package = {
      metadata: metadata,
      data: encrypted,
    };

    return this.ipfs.add(JSON.stringify(package));
  }

  async retrieveDecrypted(cid: string, requester: string): Promise<any> {
    // Get from IPFS
    const packageData = await this.ipfs.get(cid);
    const package = JSON.parse(packageData);

    // Check access
    if (!this.checkAccess(package.metadata.accessPolicy, requester)) {
      throw new Error("Access denied");
    }

    // Decrypt and return
    const decrypted = await this.decrypt(package.data);
    return JSON.parse(decrypted);
  }
}

Performance Optimization

Caching Strategies

Different nodes optimize for their workloads:

Node TypeCache FocusStrategy
snrdMPC shares, Vault configsLRU cache
hwayStatic assets, SessionsCDN + Redis
motrWASM modules, Compute resultsFile + LRU

Intelligent Prefetching

Prefetch related content for performance:

class IPFSPrefetcher {
  async prefetchVaultDependencies(vaultCID: string): Promise<void> {
    const config = await this.ipfs.get(vaultCID);
    const vault = JSON.parse(config);

    // Prefetch MPC shares
    if (vault.mpcConfig) {
      vault.mpcConfig.shareNodes.forEach((shareID) => {
        this.ipfs.get(shareID).catch(() => {}); // Background
      });
    }

    // Prefetch pinned content
    if (vault.ipfsConfig?.pinned) {
      vault.ipfsConfig.pinned.forEach((cid) => {
        this.ipfs.get(cid).catch(() => {}); // Background
      });
    }
  }
}

Developer Integration

SDK Usage

Simple SDK for IPFS operations:

import { SonrIPFS } from "@sonr/ipfs-sdk";

// Connect to private network
const ipfs = new SonrIPFS({
  network: "private",
  authentication: {
    did: "did:sonr:developer",
    signature: "signed_token",
  },
});

// Store with encryption
const cid = await ipfs.vault.store({
  data: { user: "alice", balance: 1000 },
  encryption: true,
  replication: 3,
});

// Retrieve and decrypt
const data = await ipfs.vault.retrieve(cid, {
  requester: "did:sonr:alice",
  decrypt: true,
});

// Check MPC shares
const status = await ipfs.mpc.checkShares("share_id");
console.log(`Available: ${status.available}/${status.total}`);

Monitoring

Network Health

Track IPFS network metrics:

interface IPFSMetrics {
  connectedPeers: number;
  totalStorage: number;
  replicationHealth: {
    underReplicated: string[];
    overReplicated: string[];
  };
  performance: {
    avgRetrieval: number; // ms
    avgStorage: number; // ms
    bandwidth: number; // bytes/sec
  };
}

class IPFSMonitor {
  async collectMetrics(): Promise<IPFSMetrics> {
    const peers = await this.ipfs.swarm.peers();
    const stats = await this.ipfs.stats.repo();

    return {
      connectedPeers: peers.length,
      totalStorage: stats.size,
      replicationHealth: await this.checkReplication(),
      performance: await this.measurePerformance(),
    };
  }
}

Best Practices

For Operators

  1. Monitor replication: Ensure adequate share distribution
  2. Manage storage: Set appropriate garbage collection policies
  3. Secure bootstrap: Protect bootstrap node access
  4. Update peers: Maintain peer allowlist
  5. Monitor bandwidth: Track network usage

For Developers

  1. Encrypt sensitive data: Always encrypt before storage
  2. Pin important data: Prevent garbage collection
  3. Use content addressing: Leverage deduplication
  4. Implement caching: Reduce network requests
  5. Handle failures: Design for network partitions

Next Steps

Ready to Build?

Operators: Read the Node Setup Guide for IPFS configuration.

Developers: Explore the IPFS SDK Reference for integration.

Architects: Review MPC Architecture for share management.

Learn how IPFS integrates with other Sonr components by exploring the DWN Module for Vault storage or Identity System for access control.