Skip to main content

Overview

The PDPVerifier contract provides neutral cryptographic proof verification for data storage. It:
  • Validates storage proofs from providers
  • Has NO business logic or payment handling
  • Delegates to service contracts via callbacks
  • Ensures proof integrity and timing

Design Philosophy

Separation of Concerns:
  • PDPVerifier = Protocol layer (proof verification)
  • FWSS = Service layer (business logic, payments)
This allows multiple storage services to use the same verifier.

Architecture

Data Structures

PieceInfo

struct PieceInfo {
    bytes32 pieceCid;
    uint256 size;
}

ProofData

struct ProofData {
    uint256 dataSetId;
    bytes32 challenge;
    bytes32 response;
    uint256 timestamp;
}

Key Functions

addPieces

Called by storage provider (Curio) to register pieces:
function addPieces(
    uint256 dataSetId,
    PieceInfo[] calldata pieces,
    bytes calldata extraData
) external;
Flow:
  1. Calls beforeAddPieces() on service contract
  2. Service validates extraData (EIP-712 signature)
  3. Service returns authorization hash
  4. Verifier stores pieces

submitProof

Storage provider submits periodic proofs:
function submitProof(
    uint256 dataSetId,
    bytes calldata proof
) external;
Flow:
  1. Validates proof cryptographically
  2. Calls onProofSuccess() or onProofFailure() on service
  3. Service handles payment settlement/penalties

Challenge Generation

Verifier generates random challenges:
function generateChallenge(
    uint256 dataSetId
) external view returns (bytes32);

PDPListener Interface

Service contracts implement this interface:
interface IPDPListener {
    function beforeAddPieces(
        uint256 dataSetId,
        PieceInfo[] calldata pieces,
        bytes calldata extraData
    ) external returns (bytes32 authHash);
    
    function onProofSuccess(
        uint256 dataSetId
    ) external;
    
    function onProofFailure(
        uint256 dataSetId
    ) external;
}

Read Operations

Get Data Set Info

import * as PDPVerifier from '@filoz/synapse-core/pdp-verifier'
import { createPublicClient, http } from 'viem'
import { calibration } from '@filoz/synapse-core/chains'

const client = createPublicClient({
  chain: calibration,
  transport: http(),
})

const dataSet = await PDPVerifier.getDataSet(client, { 
  dataSetId: 123n 
})

if (dataSet) {
  console.log('Service:', dataSet.service)
  console.log('Provider:', dataSet.provider)
  console.log('Piece count:', dataSet.pieceCount)
}

Get Piece Info

const piece = await PDPVerifier.getPiece(client, { 
  pieceId: 456n 
})

console.log('PieceCID:', piece.pieceCid)
console.log('Size:', piece.size)
console.log('Data Set:', piece.dataSetId)

Get Challenge

const challenge = await PDPVerifier.getChallenge(client, { 
  dataSetId: 123n 
})

console.log('Current challenge:', challenge)

Proof Verification

Proof Algorithm

PDP uses a challenge-response protocol:
  1. Challenge: Verifier generates random challenge
  2. Response: Provider computes proof using stored data
  3. Verification: Verifier validates proof cryptographically

Challenge Period

Proofs are required at regular intervals:
import { TIME_CONSTANTS } from '@filoz/synapse-sdk'

// Filecoin epoch = 30 seconds
console.log('Epoch duration:', TIME_CONSTANTS.EPOCH_DURATION)

// Challenge frequency (varies by configuration)
const challengePeriodEpochs = 2880 // Example: daily
const challengePeriodSeconds = challengePeriodEpochs * TIME_CONSTANTS.EPOCH_DURATION

console.log(`Challenge every ${challengePeriodSeconds / 3600} hours`)

Events

PiecesAdded

event PiecesAdded(
    uint256 indexed dataSetId,
    uint256[] pieceIds
);

ProofSubmitted

event ProofSubmitted(
    uint256 indexed dataSetId,
    bool success
);

Listen for Proof Events

import { watchContractEvent } from 'viem/actions'

const unwatch = watchContractEvent(client, {
  address: calibration.contracts.pdpVerifier.address,
  abi: calibration.contracts.pdpVerifier.abi,
  eventName: 'ProofSubmitted',
  args: {
    dataSetId: 123n,
  },
  onLogs: (logs) => {
    for (const log of logs) {
      console.log('Proof submitted:', log.args.success ? 'valid' : 'invalid')
    }
  },
})

Integration with FWSS

Authorization Flow

// 1. Client signs EIP-712 (handled by SDK)
import { signCreateDataSet } from '@filoz/synapse-core/typed-data'

const signature = await signCreateDataSet(client, {
  dataSetInfo: {...},
  nonce,
})

// 2. Provider calls PDPVerifier.addPieces(extraData=signature)
// 3. PDPVerifier calls FWSS.beforeAddPieces(extraData)
// 4. FWSS validates signature and returns authHash
// 5. PDPVerifier stores pieces with authHash

Proof Callback Flow

// 1. Provider calls PDPVerifier.submitProof()
// 2. PDPVerifier validates proof
// 3. PDPVerifier calls FWSS.onProofSuccess() or FWSS.onProofFailure()
// 4. FWSS settles payment rail or applies penalty

Security

Replay Protection

Challenges are unique per period:
mapping(uint256 => mapping(uint256 => bytes32)) public challenges;
// dataSetId => epoch => challenge

Proof Freshness

Proofs must be submitted within time window:
require(
    block.timestamp <= challenge.timestamp + PROOF_WINDOW,
    "Proof expired"
);

Service Authorization

Only registered services can create data sets:
mapping(address => bool) public registeredServices;

modifier onlyRegisteredService() {
    require(registeredServices[msg.sender], "Not registered");
    _;
}

Provider Operations

These operations are typically performed by Curio (storage provider software), not directly by clients.

Add Pieces (Provider)

// Curio calls this after receiving upload from client
import { addPieces } from '@filoz/synapse-core/pdp-verifier'

const hash = await addPieces(curioClient, {
  dataSetId: 123n,
  pieces: [
    { pieceCid: '0x...', size: 1024n },
    { pieceCid: '0x...', size: 2048n },
  ],
  extraData: clientSignature, // EIP-712 signature from client
})

Submit Proof (Provider)

import { submitProof } from '@filoz/synapse-core/pdp-verifier'

const proof = await curioClient.generateProof(dataSetId, challenge)

const hash = await submitProof(curioClient, {
  dataSetId: 123n,
  proof,
})

console.log('Proof submitted:', hash)

Best Practices

Use Service Layer

Interact via FWSS, not directly with verifier

Monitor Proofs

Track proof success/failure events

Trust Callbacks

Business logic belongs in service callbacks

Verify Timing

Ensure proofs are submitted on time

Specification

PDP Design Document

Read the full PDP verification specification

Source Code

PDP Verifier

View the PDPVerifier contract source

Next Steps

FWSS Contract

Learn about the service layer

Architecture

Understand contract interactions

Build docs developers (and LLMs) love