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

# Signature Gating Hook

> Pre-intent hook for off-chain signature-based taker validation

## Overview

`SignatureGatingPreIntentHook` is a pre-intent hook that validates taker eligibility using off-chain signatures. It enables depositors to control who can signal intents against their deposits without maintaining on-chain whitelists.

**Contract Location:** `contracts/hooks/SignatureGatingPreIntentHook.sol`

**Interface:** `IPreIntentHook`

**Compatible With:** OrchestratorV2 (pre-intent hooks only supported in V2)

## Use Cases

### Dynamic Access Control

Allow takers based on off-chain criteria:

* KYC/AML compliance checks
* Credit scoring
* Rate limiting
* Time-based access
* Custom business logic

### Selective Liquidity Provision

Depositors can:

* Gate intents to verified users only
* Implement per-taker limits
* Control access without gas costs for whitelist updates
* Revoke access instantly by not signing new requests

### Privacy-Preserving Gating

Off-chain signatures enable:

* Private eligibility criteria (not visible on-chain)
* Dynamic policy changes without transactions
* Reduced on-chain footprint

## Architecture

### Two-Phase Authorization

1. **Setup Phase:** Depositor sets authorized signer for their deposit
2. **Signal Phase:** Taker obtains signature from signer and includes it in `signalIntent`

### Signature Payload

The signature commits to:

* Orchestrator address (prevents cross-orchestrator replay)
* Escrow and deposit ID (binds to specific deposit)
* Intent amount (prevents amount manipulation)
* Taker address (binds signature to specific user)
* Recipient address (prevents redirection)
* Payment method and fiat currency
* Conversion rate (prevents rate manipulation)
* Referrer and fees
* Signature expiration (time-bound validity)
* Chain ID (prevents cross-chain replay)

## Data Structures

### PreIntentHookData

Passed in `SignalIntentParams.preIntentHookData`:

```solidity theme={null}
bytes memory preIntentHookData = abi.encode(
    signature,              // bytes - EIP-191 signature from authorized signer
    signatureExpiration     // uint256 - Unix timestamp when signature expires
);
```

**Fields:**

* `signature`: EIP-191 signed message hash from the authorized signer
* `signatureExpiration`: Timestamp after which signature is invalid

## Storage

### Deposit Signer Mapping

```solidity theme={null}
// escrow => depositId => authorized signer
mapping(address => mapping(uint256 => address)) public depositSigner;
```

Each deposit can have one authorized signer. Set to `address(0)` to disable gating.

## Functions

### setDepositSigner

Sets or clears the authorized signer for a deposit.

```solidity theme={null}
function setDepositSigner(
    address _escrow,
    uint256 _depositId,
    address _signer
) external;
```

**Parameters:**

* `_escrow`: Escrow contract address
* `_depositId`: Deposit ID
* `_signer`: Authorized signer address (use `address(0)` to remove)

**Authorization:** Only callable by deposit owner or delegate

**Events:**

```solidity theme={null}
event DepositSignerSet(
    address indexed escrow,
    uint256 indexed depositId,
    address indexed signer,
    address setter
);
```

**Example:**

```solidity theme={null}
// Set signer
signatureGatingHook.setDepositSigner(
    escrowAddress,
    depositId,
    signerAddress
);

// Remove signer (disable gating)
signatureGatingHook.setDepositSigner(
    escrowAddress,
    depositId,
    address(0)
);
```

### getDepositSigner

Returns the authorized signer for a deposit.

```solidity theme={null}
function getDepositSigner(
    address _escrow,
    uint256 _depositId
) external view returns (address);
```

### validateSignalIntent

Validates the signature when taker signals intent (called by orchestrator).

```solidity theme={null}
function validateSignalIntent(
    PreIntentContext calldata _ctx
) external view override;
```

**Validation Steps:**

1. Verify caller is authorized orchestrator
2. Retrieve authorized signer for deposit
3. Decode signature and expiration from `_ctx.preIntentHookData`
4. Verify signature has not expired
5. Reconstruct signed message from context
6. Verify signature is from authorized signer

**Reverts:**

* `UnauthorizedOrchestratorCaller`: Caller is not an authorized orchestrator
* `SignerNotSet`: No signer configured for this deposit
* `SignatureExpired`: Signature timestamp has passed
* `InvalidSignature`: Signature verification failed

## Usage

### Setup: Deploy and Configure

**1. Deploy Hook:**

```solidity theme={null}
SignatureGatingPreIntentHook hook = new SignatureGatingPreIntentHook(
    orchestratorRegistryAddress,
    block.chainid
);
```

**2. Set Signer for Deposit:**

```solidity theme={null}
// As deposit owner
hook.setDepositSigner(escrowAddress, depositId, signerAddress);
```

**3. Configure Deposit to Use Hook:**

```solidity theme={null}
orchestratorV2.setDepositPreIntentHook(
    escrowAddress,
    depositId,
    IPreIntentHook(address(hook))
);
```

### Off-Chain: Generate Signature

The authorized signer generates signatures off-chain:

**TypeScript/JavaScript Example:**

```typescript theme={null}
import { ethers } from 'ethers';

// Signer's wallet
const signer = new ethers.Wallet(privateKey);

// Prepare message payload (matches contract's message construction)
const message = ethers.utils.solidityPack(
  [
    'address',   // orchestrator
    'address',   // escrow
    'uint256',   // depositId
    'uint256',   // amount
    'address',   // taker
    'address',   // to
    'bytes32',   // paymentMethod
    'bytes32',   // fiatCurrency
    'uint256',   // conversionRate
    'address',   // referrer
    'uint256',   // referrerFee
    'uint256',   // signatureExpiration
    'uint256'    // chainId
  ],
  [
    orchestratorAddress,
    escrowAddress,
    depositId,
    amount,
    takerAddress,
    recipientAddress,
    paymentMethod,
    fiatCurrency,
    conversionRate,
    referrerAddress,
    referrerFee,
    signatureExpiration,  // e.g., Math.floor(Date.now() / 1000) + 3600 (1 hour)
    chainId
  ]
);

// Sign with EIP-191 prefix
const messageHash = ethers.utils.keccak256(message);
const signature = await signer.signMessage(ethers.utils.arrayify(messageHash));

console.log('Signature:', signature);
console.log('Expiration:', signatureExpiration);
```

**Python Example:**

```python theme={null}
from eth_account import Account
from eth_account.messages import encode_defunct
from web3 import Web3
import time

# Signer's private key
account = Account.from_key(private_key)

# Prepare message payload
message = Web3.solidity_keccak(
    [
        'address', 'address', 'uint256', 'uint256', 'address', 'address',
        'bytes32', 'bytes32', 'uint256', 'address', 'uint256', 'uint256', 'uint256'
    ],
    [
        orchestrator_address,
        escrow_address,
        deposit_id,
        amount,
        taker_address,
        recipient_address,
        payment_method,
        fiat_currency,
        conversion_rate,
        referrer_address,
        referrer_fee,
        int(time.time()) + 3600,  # expiration: 1 hour from now
        chain_id
    ]
)

# Sign with EIP-191
signable_message = encode_defunct(message)
signed = account.sign_message(signable_message)

print(f"Signature: {signed.signature.hex()}")
print(f"Expiration: {expiration}")
```

### On-Chain: Signal Intent with Signature

**Taker calls signalIntent with signature:**

```solidity theme={null}
// Encode hook data
bytes memory preIntentHookData = abi.encode(
    signature,              // From off-chain signer
    signatureExpiration     // From off-chain signer
);

// Signal intent
SignalIntentParams memory params = SignalIntentParams({
    escrow: escrowAddress,
    depositId: depositId,
    amount: amount,
    to: recipientAddress,
    paymentMethod: paymentMethod,
    fiatCurrency: fiatCurrency,
    conversionRate: conversionRate,
    payeeId: payeeId,
    referrer: referrerAddress,
    referrerFee: referrerFee,
    postIntentHook: address(0),  // No post-intent hook in this example
    signalHookData: "",          // No post-intent hook data
    preIntentHookData: preIntentHookData  // Signature gating data
});

orchestratorV2.signalIntent(params);
```

## Execution Flow

```mermaid theme={null}
sequenceDiagram
    participant Depositor
    participant Signer
    participant Taker
    participant Orchestrator
    participant Hook
    
    Note over Depositor: Setup Phase
    Depositor->>Hook: setDepositSigner(escrow, depositId, signer)
    Hook->>Hook: Store signer address
    Depositor->>Orchestrator: setDepositPreIntentHook(hook)
    
    Note over Taker: Request Phase
    Taker->>Signer: Request signature (off-chain)
    Signer->>Signer: Validate taker eligibility (off-chain)
    Signer->>Taker: Return signature + expiration
    
    Note over Taker: Signal Phase
    Taker->>Orchestrator: signalIntent(params)
    Orchestrator->>Hook: validateSignalIntent(ctx)
    
    alt Valid Signature
        Hook-->>Orchestrator: pass
        Orchestrator->>Orchestrator: Create intent
        Orchestrator-->>Taker: success
    else Invalid/Expired
        Hook-->>Orchestrator: revert
        Orchestrator-->>Taker: revert
    end
```

## Events

### DepositSignerSet

Emitted when signer is set or updated:

```solidity theme={null}
event DepositSignerSet(
    address indexed escrow,
    uint256 indexed depositId,
    address indexed signer,
    address setter
);
```

**Parameters:**

* `escrow`: Escrow contract address
* `depositId`: Deposit ID
* `signer`: New signer address (or address(0) if removed)
* `setter`: Address that called setDepositSigner (owner or delegate)

## Errors

```solidity theme={null}
error ZeroAddress();
error UnauthorizedCallerOrDelegate(address caller, address owner, address delegate);
error UnauthorizedOrchestratorCaller(address caller);
error SignerNotSet(address escrow, uint256 depositId);
error SignatureExpired(uint256 expiration, uint256 currentTime);
error InvalidSignature();
```

## Security Considerations

### Signature Expiration

**Always set reasonable expiration times:**

* Too short: Taker may not have time to submit transaction
* Too long: Increases replay window if conditions change
* Recommended: 5-60 minutes depending on use case

**Example:**

```javascript theme={null}
const expiration = Math.floor(Date.now() / 1000) + 1800; // 30 minutes
```

### Replay Protection

Signature commits to:

* **Chain ID**: Prevents cross-chain replay
* **Orchestrator address**: Prevents cross-orchestrator replay
* **Deposit ID**: Binds to specific deposit
* **Taker address**: Binds to specific user
* **All intent parameters**: Prevents parameter manipulation

### Signer Key Security

**Signer private key should:**

* Be stored securely (HSM, secure enclave, or encrypted storage)
* Not be reused for other purposes
* Have revocation mechanism (change signer via `setDepositSigner`)
* Be rotated periodically

**For production:**

```solidity theme={null}
// Rotate signer
hook.setDepositSigner(escrowAddress, depositId, newSignerAddress);
```

### Signature Verification

The hook uses OpenZeppelin's `SignatureChecker` which supports:

* EOA signatures (ECDSA)
* Smart contract signatures (EIP-1271)

This allows both externally owned accounts and smart contracts to act as signers.

### DoS Considerations

**Preventing DoS:**

* Signatures expire automatically (no need to revoke)
* Signer can be changed instantly by depositor
* No on-chain storage per signature (gas-efficient)

**Attack mitigation:**

* Rate limit signature generation off-chain
* Monitor for signature request abuse
* Implement IP-based or account-based throttling in signer service

## Implementation Examples

### Example 1: KYC Gating

**Setup:**

```solidity theme={null}
// Deploy hook
SignatureGatingPreIntentHook hook = new SignatureGatingPreIntentHook(
    orchestratorRegistry,
    1  // Ethereum mainnet
);

// Set KYC service as signer
hook.setDepositSigner(escrowAddress, depositId, kycServiceAddress);
orchestratorV2.setDepositPreIntentHook(escrowAddress, depositId, hook);
```

**Off-chain KYC Service:**

```typescript theme={null}
// API endpoint: POST /api/request-signature
app.post('/api/request-signature', async (req, res) => {
  const { taker, depositId, amount, ... } = req.body;
  
  // Verify KYC status
  const kycStatus = await checkKYC(taker);
  if (!kycStatus.approved) {
    return res.status(403).json({ error: 'KYC not approved' });
  }
  
  // Generate signature
  const expiration = Math.floor(Date.now() / 1000) + 1800; // 30 min
  const signature = await generateSignature({ taker, depositId, amount, expiration });
  
  res.json({ signature, expiration });
});
```

### Example 2: Rate Limiting

**Off-chain Rate Limiter:**

```typescript theme={null}
const userLimits = new Map<string, number>();

app.post('/api/request-signature', async (req, res) => {
  const { taker, amount } = req.body;
  
  // Check daily limit
  const dailyUsage = userLimits.get(taker) || 0;
  const DAILY_LIMIT = 10000e6; // 10,000 USDC
  
  if (dailyUsage + amount > DAILY_LIMIT) {
    return res.status(429).json({ error: 'Daily limit exceeded' });
  }
  
  // Generate signature
  const signature = await generateSignature(req.body);
  
  // Update usage
  userLimits.set(taker, dailyUsage + amount);
  
  res.json({ signature });
});
```

### Example 3: Time-Based Access

```typescript theme={null}
app.post('/api/request-signature', async (req, res) => {
  const { taker } = req.body;
  
  // Only allow during business hours (9 AM - 5 PM UTC)
  const hour = new Date().getUTCHours();
  if (hour < 9 || hour >= 17) {
    return res.status(403).json({ error: 'Outside business hours' });
  }
  
  const signature = await generateSignature(req.body);
  res.json({ signature });
});
```

## Comparison with Whitelist Hook

| Feature         | SignatureGatingHook                  | WhitelistHook                        |
| --------------- | ------------------------------------ | ------------------------------------ |
| **Gas Cost**    | No gas for eligibility changes       | Gas required to add/remove addresses |
| **Privacy**     | Criteria can be off-chain            | All addresses visible on-chain       |
| **Flexibility** | Dynamic, per-request validation      | Static list                          |
| **Revocation**  | Automatic via expiration             | Requires transaction                 |
| **Scalability** | Unlimited users                      | Limited by gas costs                 |
| **Complexity**  | Requires off-chain signer service    | Pure on-chain                        |
| **Best For**    | Dynamic criteria, KYC, rate limiting | Small, stable user sets              |

## Related Contracts

* [Hooks Overview](/contracts/hooks-overview) - Hook system architecture
* [WhitelistPreIntentHook](/contracts/whitelist-hook) - Alternative on-chain whitelist
* [OrchestratorV2](/contracts/orchestrator) - V2 orchestrator with pre-intent hooks
* [OrchestratorRegistry](/contracts/orchestrator-registry) - Authorized orchestrator registry
