> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/zkp2p/zkp2p-contracts/llms.txt
> Use this file to discover all available pages before exploring further.

# Payment Verification

> Unified verifier architecture, attestation verification, and payment proof validation

## 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.

<Info>
  The system replaced individual payment verifiers (VenmoVerifier, PayPalVerifier, etc.) with the UnifiedPaymentVerifier for easier maintenance and upgrades.
</Info>

## Architecture Overview

```mermaid theme={null}
graph TD
    A[Orchestrator] -->|verifyPayment| B[UnifiedPaymentVerifier]
    B -->|verify attestation| C[AttestationVerifier]
    B -->|check nullifier| D[NullifierRegistry]
    B -->|fetch intent data| A
    C -->|validate signatures| E[Attestation Service]
    D -->|prevent double-spend| F[Nullifier Storage]
    
    style B fill:#e1f5ff
    style C fill:#fff4e6
    style D fill:#ffe6e6
```

## 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`):

```solidity theme={null}
function verify(
    bytes32 _digest,           // EIP-712 formatted message digest
    bytes[] calldata _sigs,    // Array of signatures from attestors
    bytes calldata _data       // Verification data (attestor identities, hints)
) external view returns (bool isValid);
```

### 3. NullifierRegistry

Prevents double-spending by tracking used payment IDs (from `contracts/registries/NullifierRegistry.sol`).

**Key Functions**:

```solidity theme={null}
function addNullifier(bytes32 _nullifier) external onlyWriter;
function isNullified(bytes32 _nullifier) external view returns (bool);
```

<Warning>
  Only addresses with write permissions (payment verifiers) can add nullifiers. This prevents griefing attacks.
</Warning>

## Payment Verification Flow

```mermaid theme={null}
sequenceDiagram
    participant User
    participant Orchestrator
    participant Verifier as UnifiedPaymentVerifier
    participant Attestation as AttestationVerifier
    participant Nullifier as NullifierRegistry
    
    User->>Orchestrator: fulfillIntent(proof)
    Orchestrator->>Verifier: verifyPayment(intentHash, proof)
    Verifier->>Verifier: Decode PaymentAttestation
    Verifier->>Verifier: Decode PaymentDetails & IntentSnapshot
    Verifier->>Orchestrator: Fetch Intent data
    Verifier->>Verifier: Validate snapshot against intent
    Verifier->>Attestation: verify(digest, signatures)
    Attestation-->>Verifier: isValid
    Verifier->>Nullifier: isNullified(paymentId)
    Nullifier-->>Verifier: false
    Verifier->>Nullifier: addNullifier(paymentId)
    Verifier->>Verifier: Calculate release amount
    Verifier-->>Orchestrator: PaymentVerificationResult
```

## Data Structures

### PaymentAttestation

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

```solidity theme={null}
struct PaymentAttestation {
    bytes32 intentHash;       // Binds the payment to the intent on Orchestrator
    uint256 releaseAmount;    // Final token amount to release on-chain after FX
    bytes32 dataHash;         // Hash of the additional data to verify integrity
    bytes[] signatures;       // Array of signatures from witnesses
    bytes data;               // Data for verification (PaymentDetails + IntentSnapshot)
    bytes metadata;           // Additional metadata; isn't signed by the witnesses
}
```

### PaymentDetails

Detailed information about the off-chain payment (from `contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:65`):

```solidity theme={null}
struct PaymentDetails {
    bytes32 method;           // Payment method hash (e.g., keccak256("venmo"))
    bytes32 payeeId;          // Payment recipient ID (hashed to preserve privacy)
    uint256 amount;           // Payment amount in smallest currency unit (cents)
    bytes32 currency;         // Payment currency hash (e.g., keccak256("USD"))
    uint256 timestamp;        // Payment timestamp in UTC milliseconds
    bytes32 paymentId;        // Hashed payment identifier from the service
}
```

<Tip>
  All sensitive fields (payeeId, paymentId) are hashed before going on-chain to preserve privacy.
</Tip>

### IntentSnapshot

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

```solidity theme={null}
struct IntentSnapshot {
    bytes32 intentHash;
    uint256 amount;
    bytes32 paymentMethod;
    bytes32 fiatCurrency;
    bytes32 payeeDetails;
    uint256 conversionRate;
    uint256 signalTimestamp;
    uint256 timestampBuffer;      // Max allowed time between payment and fulfillment
}
```

## Verification Process

### Step 1: Decode Attestation

```solidity theme={null}
PaymentAttestation memory attestation = _decodeAttestation(_verifyPaymentData.paymentProof);
```

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

### Step 2: Decode Payload

```solidity theme={null}
(PaymentDetails memory paymentDetails, IntentSnapshot memory intentSnapshot) = 
    _decodeAttestationPayload(attestation.data);
```

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

### Step 3: Validate Payment Method

```solidity theme={null}
require(isPaymentMethod[paymentDetails.method], "UPV: Invalid 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`):

```solidity theme={null}
function _validateIntentSnapshot(
    bytes32 intentHash,
    IntentSnapshot memory snapshot
) internal view {
    require(snapshot.intentHash == intentHash, "UPV: Snapshot hash mismatch");
    
    IOrchestrator.Intent memory intent = IOrchestrator(msg.sender).getIntent(intentHash);
    require(snapshot.payeeDetails == intent.payeeId, "UPV: Snapshot payee mismatch");
    require(snapshot.amount == intent.amount, "UPV: Snapshot amount mismatch");
    require(snapshot.paymentMethod == intent.paymentMethod, "UPV: Snapshot method mismatch");
    require(snapshot.fiatCurrency == intent.fiatCurrency, "UPV: Snapshot currency mismatch");
    require(snapshot.conversionRate == intent.conversionRate, "UPV: Snapshot rate mismatch");
    require(snapshot.signalTimestamp == intent.timestamp, "UPV: Snapshot timestamp mismatch");
    require(snapshot.timestampBuffer <= MAX_TIMESTAMP_BUFFER, "UPV: Timestamp buffer exceeds maximum");
}
```

<Info>
  **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.
</Info>

### Step 5: Verify Attestation

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

```solidity theme={null}
function _verifyAttestation(PaymentAttestation memory attestation) internal view returns (bool) {
    // Construct EIP-712 struct hash
    bytes32 structHash = keccak256(
        abi.encode(
            PAYMENT_ATTESTATION_TYPEHASH,
            attestation.intentHash,
            attestation.releaseAmount,
            attestation.dataHash
        )
    );
    
    // Construct EIP-712 digest
    bytes32 digest = keccak256(
        abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR,
            structHash
        )
    );
    
    // Verify data integrity
    require(
        keccak256(attestation.data) == attestation.dataHash,
        "UPV: Data hash mismatch"
    );
    
    // Verify signatures via AttestationVerifier
    bool isValid = attestationVerifier.verify(
        digest, 
        attestation.signatures,
        attestation.data
    );
    
    return isValid;
}
```

### EIP-712 Domain

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

```solidity theme={null}
DOMAIN_SEPARATOR = keccak256(
    abi.encode(
        DOMAIN_TYPEHASH,
        keccak256(bytes("UnifiedPaymentVerifier")), // name
        keccak256(bytes("1")),                      // version
        block.chainid,                              // chainId
        address(this)                               // verifyingContract
    )
);
```

### EIP-712 Type Hash

```solidity theme={null}
bytes32 private constant PAYMENT_ATTESTATION_TYPEHASH = keccak256(
    "PaymentAttestation(bytes32 intentHash,uint256 releaseAmount,bytes32 dataHash)"
);
```

### Step 6: Nullify Payment

To prevent double-spending, the payment ID is nullified (from `contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:242`):

```solidity theme={null}
function _nullifyPayment(bytes32 paymentMethod, bytes32 paymentId) internal {
    bytes32 nullifier = keccak256(abi.encodePacked(paymentMethod, paymentId));
    _validateAndAddNullifier(nullifier);
}
```

<Note>
  The nullifier includes both payment method and payment ID to prevent collisions (e.g., Venmo transaction #123 vs PayPal transaction #123).
</Note>

### Step 7: Calculate Release Amount

The release amount can be capped to the intent amount (from `contracts/unifiedVerifier/UnifiedPaymentVerifier.sol:250`):

```solidity theme={null}
function _calculateReleaseAmount(uint256 releaseAmount, uint256 intentAmount) 
    internal pure returns (uint256) 
{
    if (releaseAmount > intentAmount) {
        return intentAmount;
    }
    return releaseAmount;
}
```

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`):

```solidity theme={null}
emit PaymentVerified(
    intentHash,
    paymentDetails.method,
    paymentDetails.currency,
    paymentDetails.amount,
    paymentDetails.timestamp,
    paymentDetails.paymentId,
    paymentDetails.payeeId
);
```

### Step 9: Return Result

```solidity theme={null}
result = PaymentVerificationResult({
    success: true,
    intentHash: attestation.intentHash,
    releaseAmount: releaseAmount
});
```

## Base Verifier Architecture

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

### Payment Method Management

```solidity theme={null}
function addPaymentMethod(bytes32 _paymentMethod) external onlyOwner
function removePaymentMethod(bytes32 _paymentMethod) external onlyOwner
```

Payment methods are identified by their keccak256 hash:

```solidity theme={null}
bytes32 venmoMethod = keccak256("venmo");
bytes32 paypalMethod = keccak256("paypal");
```

### Attestation Verifier Management

```solidity theme={null}
function setAttestationVerifier(address _newVerifier) external onlyOwner
```

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`):

```solidity theme={null}
modifier onlyOrchestrator() {
    require(orchestratorRegistry.isOrchestrator(msg.sender), "Only orchestrator can call");
    _;
}
```

## Nullifier Registry

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

### Write Permissions

Only addresses with write permissions can add nullifiers:

```solidity theme={null}
function addWritePermission(address _newWriter) external onlyOwner
function removeWritePermission(address _removedWriter) external onlyOwner
```

Typically, payment verifiers are granted write permissions.

### Adding Nullifiers

```solidity theme={null}
function addNullifier(bytes32 _nullifier) external onlyWriter {
    require(!isNullified[_nullifier], "Nullifier already exists");
    isNullified[_nullifier] = true;
    emit NullifierAdded(_nullifier, msg.sender);
}
```

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

## Payment Method Registry

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

### Adding Payment Methods

```solidity theme={null}
function addPaymentMethod(
    bytes32 _paymentMethod,
    address _verifier,
    bytes32[] calldata _currencies
) external onlyOwner
```

**Example**:

```solidity theme={null}
registry.addPaymentMethod(
    keccak256("venmo"),
    0x123...UnifiedVerifier,
    [
        keccak256("USD")
    ]
);
```

### Managing Currencies

```solidity theme={null}
function addCurrencies(bytes32 _paymentMethod, bytes32[] calldata _currencies) external onlyOwner
function removeCurrencies(bytes32 _paymentMethod, bytes32[] calldata _currencies) external onlyOwner
```

### Querying

```solidity theme={null}
function getVerifier(bytes32 _paymentMethod) external view returns (address)
function isCurrency(bytes32 _paymentMethod, bytes32 _currencyCode) external view returns (bool)
function getCurrencies(bytes32 _paymentMethod) external view returns (bytes32[] memory)
```

## 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

<CardGroup cols={2}>
  <Card title="TEE-based" icon="microchip">
    Trusted Execution Environments (SGX, TDX) verify payments in hardware enclaves
  </Card>

  <Card title="Multi-Witness" icon="users">
    Multiple independent attestors sign payment verification (threshold signatures)
  </Card>

  <Card title="ZK Proofs" icon="shield-halved">
    Zero-knowledge proofs of email receipts or bank statements
  </Card>

  <Card title="Hybrid" icon="layer-group">
    Combination of TEE + ZK or TEE + multi-witness for added security
  </Card>
</CardGroup>

## Security Considerations

### Timestamp Buffer

The `timestampBuffer` in IntentSnapshot limits how old a payment can be relative to the intent signal time:

```solidity theme={null}
require(
    snapshot.timestampBuffer <= MAX_TIMESTAMP_BUFFER, 
    "UPV: Snapshot timestamp buffer exceeds maximum"
);
```

* 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:

```solidity theme={null}
require(
    keccak256(attestation.data) == attestation.dataHash,
    "UPV: Data hash mismatch"
);
```

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`):

```solidity theme={null}
function verifyPayment(VerifyPaymentData calldata _verifyPaymentData)
    external
    override
    onlyOrchestrator()
    returns (PaymentVerificationResult memory result)
```

This prevents griefing attacks where malicious actors nullify payment IDs.

## Events

```solidity theme={null}
event PaymentVerified(
    bytes32 indexed intentHash,
    bytes32 indexed method,
    bytes32 indexed currency,
    uint256 amount,
    uint256 timestamp,
    bytes32 paymentId,
    bytes32 payeeId
);

event PaymentMethodAdded(bytes32 indexed paymentMethod);
event PaymentMethodRemoved(bytes32 indexed paymentMethod);
event AttestationVerifierUpdated(address indexed oldVerifier, address indexed newVerifier);
```

## 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

<Info>
  **No Critical State**: The UnifiedPaymentVerifier holds no critical state (besides configuration). All intents and deposits live in Orchestrator and Escrow.
</Info>

## Related Contracts

* [Orchestrator](/concepts/orchestrator) - Calls verifyPayment during fulfillment
* [Intent Lifecycle](/concepts/intent-lifecycle) - Shows where verification fits in the flow
* [Registry System](/concepts/registry-system) - Payment method and currency management
