Skip to main content

Overview

The Payment Verification System validates that buyers have completed off-chain fiat payments before releasing on-chain assets. The zkp2p protocol uses a unified verifier architecture that supports multiple payment methods (Venmo, PayPal, Wise, etc.) through a single configurable contract.
The system replaced individual payment verifiers (VenmoVerifier, PayPalVerifier, etc.) with the UnifiedPaymentVerifier for easier maintenance and upgrades.

Architecture Overview

Key Components

1. UnifiedPaymentVerifier

The main verifier contract that validates payment attestations (located at contracts/unifiedVerifier/UnifiedPaymentVerifier.sol). Responsibilities:
  • Decode payment attestations
  • Validate intent snapshots against on-chain intent data
  • Verify EIP-712 attestation signatures via AttestationVerifier
  • Nullify payments to prevent double-spending
  • Calculate release amounts (supporting partial fulfillment)
  • Emit payment details for off-chain reconciliation

2. AttestationVerifier

Pluggable verifier for attestation signatures from off-chain services (witnesses, TEE, etc.). Interface (from contracts/interfaces/IAttestationVerifier.sol:16):

3. NullifierRegistry

Prevents double-spending by tracking used payment IDs (from contracts/registries/NullifierRegistry.sol). Key Functions:
Only addresses with write permissions (payment verifiers) can add nullifiers. This prevents griefing attacks.

Payment Verification Flow

Data Structures

PaymentAttestation

The top-level structure submitted as payment proof (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:85):

PaymentDetails

Detailed information about the off-chain payment (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:65):
All sensitive fields (payeeId, paymentId) are hashed before going on-chain to preserve privacy.

IntentSnapshot

Snapshot of intent parameters at signal time (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:74):

Verification Process

Step 1: Decode Attestation

The payment proof is ABI-decoded into the PaymentAttestation struct.

Step 2: Decode Payload

The attestation.data field contains both payment details and intent snapshot.

Step 3: Validate Payment Method

Ensures the payment method is registered with the verifier.

Step 4: Validate Intent Snapshot

The verifier reads the intent from the Orchestrator and validates every field (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:220):
Why validate the snapshot? This ensures the attestation service is verifying the payment against the exact same intent parameters that were locked on-chain. Any mismatch indicates tampering.

Step 5: Verify Attestation

The verifier constructs an EIP-712 digest and validates signatures (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:183):

EIP-712 Domain

The domain separator is computed at deployment (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:106):

EIP-712 Type Hash

Step 6: Nullify Payment

To prevent double-spending, the payment ID is nullified (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:242):
The nullifier includes both payment method and payment ID to prevent collisions (e.g., Venmo transaction #123 vs PayPal transaction #123).

Step 7: Calculate Release Amount

The release amount can be capped to the intent amount (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:250):
This supports partial fulfillment where the buyer pays less than the full intent amount.

Step 8: Emit Payment Details

Payment details are emitted for off-chain reconciliation and indexing (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:260):

Step 9: Return Result

Base Verifier Architecture

The BaseUnifiedPaymentVerifier (from contracts/unifiedVerifier/BaseUnifiedPaymentVerifier.sol) provides shared functionality:

Payment Method Management

Payment methods are identified by their keccak256 hash:

Attestation Verifier Management

The attestation verifier can be upgraded without redeploying the payment verifier.

Orchestrator Authorization

Only whitelisted orchestrators can call verifyPayment() (from contracts/unifiedVerifier/BaseUnifiedPaymentVerifier.sol:48):

Nullifier Registry

The NullifierRegistry prevents double-spending of payments (from contracts/registries/NullifierRegistry.sol).

Write Permissions

Only addresses with write permissions can add nullifiers:
Typically, payment verifiers are granted write permissions.

Adding Nullifiers

Irreversible: Once a nullifier is added, it cannot be removed. This is by design to prevent replay attacks.

Payment Method Registry

The PaymentVerifierRegistry (from contracts/registries/PaymentVerifierRegistry.sol) maps payment methods to verifiers and supported currencies.

Adding Payment Methods

Example:

Managing Currencies

Querying

Attestation Service Integration

The UnifiedPaymentVerifier is designed to work with off-chain attestation services that:
  1. Monitor Off-Chain Payments: Watch for payments on Venmo, PayPal, Wise, etc.
  2. Extract Payment Details: Parse amount, currency, timestamp, payment ID, etc.
  3. Fetch Intent Data: Read intent parameters from Orchestrator
  4. Calculate Release Amount: Apply conversion rate and handle partial fulfillment
  5. Sign Attestation: Create EIP-712 signature over PaymentAttestation struct
  6. Submit to Orchestrator: Anyone can call fulfillIntent() with the signed attestation

Attestation Service Types

TEE-based

Trusted Execution Environments (SGX, TDX) verify payments in hardware enclaves

Multi-Witness

Multiple independent attestors sign payment verification (threshold signatures)

ZK Proofs

Zero-knowledge proofs of email receipts or bank statements

Hybrid

Combination of TEE + ZK or TEE + multi-witness for added security

Security Considerations

Timestamp Buffer

The timestampBuffer in IntentSnapshot limits how old a payment can be relative to the intent signal time:
  • Max buffer: 48 hours (MAX_TIMESTAMP_BUFFER = 48 * 60 * 60 * 1000 at line 31)

Data Hash Integrity

The attestation includes a dataHash that must match the actual data:
This prevents attestors from signing one payload but submitting another.

Orchestrator-Only Access

The verifier can only be called by whitelisted orchestrators (from contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:128):
This prevents griefing attacks where malicious actors nullify payment IDs.

Events

Upgradeability

The payment verification system is designed for easy upgrades:
  1. Verifier Replacement: Deploy new UnifiedPaymentVerifier, update PaymentVerifierRegistry
  2. Attestation Verifier: Update via setAttestationVerifier() without redeploying main verifier
  3. Payment Methods: Add/remove payment methods via registry without contract changes
No Critical State: The UnifiedPaymentVerifier holds no critical state (besides configuration). All intents and deposits live in Orchestrator and Escrow.