OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / services / builtin / storage-proof-service.js

87 lines · javascript ref:
/**
 * StorageProofService — Tier-2 trustless seed verification (relay side).
 *
 * Answers `prove({ coreKey, index, nonce })` with a signed Hypercore Merkle
 * proof for block `index` of a SEEDED drive's metadata core, signed with the
 * relay's swarm identity key. The caller verifies it with `verifyStorageProof`
 * against the drive key alone — the relay is trusted for nothing. See
 * packages/core/core/protocol/proof-of-storage.js for the proof itself.
 *
 * SECURITY:
 *  - PRIVACY GATE: never confirms possession of a BLIND / privacy-redacted
 *    drive. A signed proof is cryptographic, relay-attributable evidence that
 *    this relay holds a key; serving it for a blind drive would defeat the
 *    catalog's deliberate redaction (AppRegistry._shouldRedactEntry). Blind/
 *    redacted keys return NOT_SEEDED — INDISTINGUISHABLE from a key the relay
 *    genuinely doesn't hold, so prove() can't be used as a possession oracle.
 *  - PHANTOM-CORE DoS GUARD: only serves keys present in node.appRegistry. It
 *    NEVER calls store.get() on a caller-supplied key (which would create an
 *    unbounded phantom Hypercore per request).
 *  - RATE LIMIT: a GLOBAL token bucket caps total proof work regardless of how
 *    many ephemeral swarm identities connect (the per-caller bucket alone is
 *    bypassable by rotating identities). The global bucket is consumed only for
 *    REAL proof work — cheap rejects (bad input / not-seeded / blind) never
 *    spend it, so a not-seeded flood can't starve honest callers. The per-caller
 *    bucket Map is idle-evicted + size-capped so it can't grow unbounded.
 *  - buildStorageProof reads LOCAL storage only and throws BLOCK_NOT_LOCAL /
 *    BLOCK_OUT_OF_RANGE if the relay doesn't actually hold the block.
 *
 * v1 proves the drive's METADATA core (drive.core) — the head the client learns
 * from open(), so its minLength pin lines up. Proving the blobs core is a
 * follow-up (needs a `core: meta|blobs` selector + a timeout-guarded getBlobs()).
 */

import { ServiceProvider } from 'p2p-hiverelay/core/services/provider.js'
import {
  PROOF_KIND_RETRIEVABILITY,
  RETRIEVABILITY_PROOF_LIMITATION,
  RETRIEVABILITY_PROOF_SIGNATURE_PROFILE,
  STORAGE_PROOF_LEGACY_SIGNATURE_PROFILE
} from 'p2p-hiverelay/core/protocol/proof-of-storage.js'
import { RetrievabilityProofProvider } from 'p2p-hiverelay/core/relay-node/retrievability-proof.js'

export class StorageProofService extends ServiceProvider {
  constructor (opts = {}) {
    super()
    this._provider = new RetrievabilityProofProvider(opts)
  }

  get _buckets () {
    return this._provider._buckets
  }

  manifest () {
    return {
      name: 'storage-proof',
      version: '1.0.0',
      description: 'Signed challenge-response proof-of-retrievability for seeded blocks',
      capabilities: ['prove'],
      proofKind: PROOF_KIND_RETRIEVABILITY,
      proofLimit: RETRIEVABILITY_PROOF_LIMITATION,
      signatureProfiles: [
        STORAGE_PROOF_LEGACY_SIGNATURE_PROFILE,
        RETRIEVABILITY_PROOF_SIGNATURE_PROFILE
      ],
      preferredSignatureProfile: RETRIEVABILITY_PROOF_SIGNATURE_PROFILE
    }
  }

  async start (context = {}) {
    this._provider.start(context)
  }

  async stop () {
    await this._provider.stop()
  }

  /**
   * Produce a signed proof of possession for block `index` of a seeded,
   * non-private drive's metadata core.
   * @param {{coreKey:string, index:number, nonce:string, signatureProfile?:string}} params
   * @param {{remotePubkey?:Buffer|string, caller?:string}} context
   */
  async prove (params = {}, context = {}) {
    return this._provider.prove(params, context)
  }
}