12 KiB
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