Files
motr-enclave/STORAGE.md

12 KiB

IPFS Storage Architecture for Motr Enclave

Decentralized storage layer for Nebula wallet's encrypted key enclave using IPFS via Helia with Circuit Relay connectivity.

Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                           NEBULA WALLET STORAGE                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────────────────┐         ┌─────────────────────────────────┐   │
│  │   Browser Environment   │         │    Sonr Relay Infrastructure    │   │
│  │                         │         │                                 │   │
│  │  ┌───────────────────┐  │   WSS   │  ┌───────────────────────────┐  │   │
│  │  │   Motr Enclave    │  │◄───────►│  │  ipfs-service-provider    │  │   │
│  │  │   (WASM Plugin)   │  │         │  │  + circuitRelayServer()   │  │   │
│  │  └───────────────────┘  │         │  └───────────────────────────┘  │   │
│  │           │             │         │              │                  │   │
│  │           ▼             │         │              ▼                  │   │
│  │  ┌───────────────────┐  │         │  ┌───────────────────────────┐  │   │
│  │  │   Helia Client    │  │         │  │      Helia Node           │  │   │
│  │  │   (Browser SDK)   │──┼─────────┼──│   (Content Pinning)       │  │   │
│  │  └───────────────────┘  │  Relay  │  └───────────────────────────┘  │   │
│  │           │             │         │              │                  │   │
│  └───────────┼─────────────┘         └──────────────┼──────────────────┘   │
│              │                                      │                      │
│              │  /p2p-circuit                        │  DHT / Bitswap       │
│              ▼                                      ▼                      │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │                         IPFS Network                                │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────────┘

Data Classification

MUST Store on IPFS

Data Type Format Mutability
Encrypted DB Blob Raw bytes Immutable (new CID per version)
DID Document DAG-CBOR Mutable via IPNS
UCAN Delegations DAG-CBOR Immutable CID references
Verification Methods DAG-CBOR Part of DID Document

MUST NOT Store on IPFS

Data Type Reason
MPC Key Shares Core secret material
WebAuthn PRF Output Decryption key derivation
Session Tokens Ephemeral, local-only
Plaintext Credentials Security violation

Browser Client Requirements

Transport Configuration

The browser Helia client MUST configure these transports:

transports: [
  webSockets({ filter: filters.all }),
  webRTC(),
  circuitRelayTransport({ discoverRelays: 2 })
]

Listen Addresses

The client MUST listen on:

addresses: {
  listen: ['/p2p-circuit', '/webrtc']
}

Required Services

The identify service is MANDATORY for Circuit Relay v2:

services: {
  identify: identify()
}

Bootstrap Peers

The client MUST bootstrap with at least 2 Sonr relay multiaddrs:

peerDiscovery: [
  bootstrap({
    list: [
      '/dns4/relay1.sonr.io/tcp/443/wss/p2p/<PEER_ID>',
      '/dns4/relay2.sonr.io/tcp/443/wss/p2p/<PEER_ID>'
    ]
  })
]

Complete Client Implementation

import { createHelia } from 'helia'
import { circuitRelayTransport } from '@libp2p/circuit-relay-v2'
import { webRTC } from '@libp2p/webrtc'
import { webSockets } from '@libp2p/websockets'
import { bootstrap } from '@libp2p/bootstrap'
import { identify } from '@libp2p/identify'
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
import * as filters from '@libp2p/websockets/filters'

const SONR_RELAYS = [
  '/dns4/relay1.sonr.io/tcp/443/wss/p2p/<PEER_ID>',
  '/dns4/relay2.sonr.io/tcp/443/wss/p2p/<PEER_ID>'
]

export async function createStorageClient(): Promise<Helia> {
  return await createHelia({
    libp2p: {
      addresses: {
        listen: ['/p2p-circuit', '/webrtc']
      },
      transports: [
        webSockets({ filter: filters.all }),
        webRTC(),
        circuitRelayTransport({ discoverRelays: 2 })
      ],
      connectionEncrypters: [noise()],
      streamMuxers: [yamux()],
      peerDiscovery: [
        bootstrap({ list: SONR_RELAYS })
      ],
      services: {
        identify: identify()
      },
      connectionGater: {
        denyDialMultiaddr: () => false
      }
    }
  })
}

Relay Server Requirements

Circuit Relay Configuration

The relay server MUST enable circuitRelayServer with these constraints:

import { circuitRelayServer } from '@libp2p/circuit-relay-v2'

services: {
  relay: circuitRelayServer({
    hopTimeout: 30 * 1000,
    reservations: {
      maxReservations: 100,
      reservationClearInterval: 300 * 1000,
      applyDefaultLimit: true,
      defaultDurationLimit: 2 * 60 * 1000,
      defaultDataLimit: BigInt(1 << 20)
    },
    maxInboundHopStreams: 64,
    maxOutboundHopStreams: 128
  })
}

Listen Addresses

The relay MUST listen on:

addresses: {
  listen: [
    '/ip4/0.0.0.0/tcp/4001',
    '/ip4/0.0.0.0/tcp/4003/ws',
    '/ip4/0.0.0.0/udp/4001/quic-v1',
    '/webrtc'
  ]
}

Environment Variables

Variable Required Description
ENABLE_CIRCUIT_RELAY Yes Set to 1
CR_DOMAIN Yes Relay domain (e.g., relay.sonr.org)
IPFS_TCP_PORT Yes TCP port (default: 4001)
IPFS_WS_PORT Yes WebSocket port (default: 4003)
COORD_NAME No Node identifier for coordination

Infrastructure Requirements

Requirement Specification
SSL/TLS Valid certificate for WSS
Reverse Proxy Nginx forwarding 443 → IPFS_WS_PORT
Redundancy Minimum 2 relay nodes
Uptime 99.9% availability target

Storage Operations

Enclave Persistence

import { unixfs } from '@helia/unixfs'
import type { CID } from 'multiformats/cid'
import type { Helia } from 'helia'

export class EnclaveStorage {
  private fs: UnixFS

  constructor(private helia: Helia) {
    this.fs = unixfs(helia)
  }

  async persist(encryptedBlob: Uint8Array): Promise<CID> {
    const cid = await this.fs.addBytes(encryptedBlob)
    await this.helia.pins.add(cid)
    return cid
  }

  async retrieve(cid: CID): Promise<Uint8Array> {
    const chunks: Uint8Array[] = []
    for await (const chunk of this.fs.cat(cid)) {
      chunks.push(chunk)
    }
    return concatUint8Arrays(chunks)
  }
}

DID Publishing

import { ipns } from '@helia/ipns'
import { dagCbor } from '@helia/dag-cbor'
import type { PrivateKey } from '@libp2p/interface'

export class DIDPublisher {
  private name: IPNS
  private cbor: DagCBOR

  constructor(private helia: Helia) {
    this.name = ipns(helia)
    this.cbor = dagCbor(helia)
  }

  async publish(didDocument: object, key: PrivateKey): Promise<string> {
    const cid = await this.cbor.add(didDocument)
    await this.name.publish(key, cid)
    return `/ipns/${key.publicKey.toCID()}`
  }

  async resolve(ipnsName: string): Promise<object> {
    const result = await this.name.resolve(ipnsName)
    return await this.cbor.get(result.cid)
  }
}

Security Requirements

Encryption

Layer Requirement
Transport TLS 1.3 (WSS) + libp2p Noise protocol
Storage AES-256-GCM with WebAuthn PRF-derived key
Integrity SHA-256 content hash (CID)

Relay Trust Model

Property Guarantee
Zero-Knowledge Relay MUST NOT see plaintext data
No Persistence Relay MUST NOT store relayed content
Rate Limiting Relay MUST enforce per-peer limits

Client Responsibilities

Responsibility Requirement
Encrypt before upload All data MUST be encrypted client-side
Verify on download Client MUST verify CID matches content
Local CID storage Latest CID MUST persist in IndexedDB
Key isolation Decryption keys MUST NOT leave browser

API Specification

StorageClient Interface

interface StorageClient {
  connect(): Promise<void>
  disconnect(): Promise<void>
  isConnected(): boolean

  persistEnclave(encrypted: Uint8Array): Promise<CID>
  retrieveEnclave(cid: CID): Promise<Uint8Array>
  
  publishDID(doc: DIDDocument, key: PrivateKey): Promise<string>
  resolveDID(name: string): Promise<DIDDocument>
  
  getStatus(): StorageStatus
}

interface StorageStatus {
  peerId: string
  connected: boolean
  peerCount: number
  relayCount: number
}

Relay API Endpoints

Endpoint Method Response
/ipfs GET { ipfsId, peers, relays }
/ipfs/node GET Full node configuration
/ipfs/relays POST Known relay list

Dependencies

Browser SDK

{
  "helia": "^5.0.0",
  "@helia/unixfs": "^4.0.0",
  "@helia/ipns": "^8.0.0",
  "@helia/dag-cbor": "^4.0.0",
  "@libp2p/circuit-relay-v2": "^3.0.0",
  "@libp2p/webrtc": "^5.0.0",
  "@libp2p/websockets": "^9.0.0",
  "@libp2p/bootstrap": "^11.0.0",
  "@libp2p/identify": "^3.0.0",
  "@chainsafe/libp2p-noise": "^16.0.0",
  "@chainsafe/libp2p-yamux": "^7.0.0",
  "multiformats": "^13.0.0"
}

Relay Server

{
  "helia": "^5.0.0",
  "helia-coord": "^3.0.0",
  "@libp2p/circuit-relay-v2": "^3.0.0",
  "@libp2p/tcp": "^10.0.0",
  "@libp2p/websockets": "^9.0.0",
  "@libp2p/webrtc": "^5.0.0"
}

File Structure

src/
├── storage/
│   ├── client.ts          # Helia client factory
│   ├── enclave.ts         # Enclave persistence
│   ├── did.ts             # DID/IPNS operations
│   └── types.ts           # TypeScript interfaces
deploy/
├── relay/
│   ├── docker-compose.yml
│   └── config.env
└── nginx/
    └── relay.conf         # WSS proxy config

References