# ACRRV — Agentic Consent Receipt + Replay Verifier

Proof-based consent verification for autonomous agents. ACRRV is a production-grade library for generating, signing, and verifying cryptographically-backed consent receipts with anti-replay protection and offline verification support.

**Features:**
- ✓ Ed25519 signatures for authentication and integrity
- ✓ Anti-replay nonce tracking (in-memory, file-based, or custom adapter)
- ✓ Offline verification with freshness windows
- ✓ Constraint enforcement (amount, merchant, time, geography)
- ✓ Express API server with CLI tools
- ✓ TypeScript with strict types
- ✓ Zero external dependencies (except noble/ed25519)

## Quick Start

### Installation

```bash
cd /var/www/vhosts/affix-io.com/httpdocs/lab/agentic-consent-receipt
npm install
npm run build
```

### Generate a Receipt

```bash
npm run cli -- generate-receipt \
  --agent shopping_agent_v1 \
  --delegator user_12345 \
  --scope pay,book,cancel \
  --max-amount 500 \
  --currency GBP \
  --merchant merchant_456 \
  --ttl 86400
```

### Verify a Receipt

```bash
npm run cli -- verify-receipt \
  --file ./examples/receipt.json \
  --action pay \
  --amount 250 \
  --currency GBP \
  --merchant merchant_456
```

### Start API Server

```bash
npm start
# Listening on http://localhost:3001
```

## Architecture

### Receipt Schema

```typescript
interface ConsentReceipt {
  version: "1.0";
  receipt_id: string;          // UUIDv4
  agent_id: string;            // Agent identifier
  delegator_id: string;        // User/org identifier (pseudonymous)
  consent_scope: string[];     // ["pay", "book", "cancel"]
  constraints: {
    max_amount?: number;
    currency?: string;
    merchant_id?: string;
    allowed_endpoints?: string[];
    allowed_categories?: string[];
    geographic_restriction?: string;
    time_window_start: string; // ISO 8601
    time_window_end: string;
    transaction_count_limit?: number;
  };
  nonce: string;               // 32 bytes hex (replay protection)
  issued_at: string;           // ISO 8601
  expiry: string;              // ISO 8601
  offline_policy?: {
    offline_verification_allowed: boolean;
    freshness_window_seconds: number;
  };
  metadata?: Record<string, string>;
  issuer?: string;
  schema_version: "1.0";
}
```

### Signed Receipt

```typescript
interface SignedReceipt {
  payload: ConsentReceipt;
  signature: string;   // base64url Ed25519 signature
  public_key: string;  // base64url encoded public key
}
```

## Verification Pipeline

The verifier runs these checks in order:

1. **Schema version** — must be "1.0"
2. **Structure** — all required fields present
3. **Signature** — Ed25519 verify over canonical JSON
4. **Expiry** — `expiry > now`
5. **Issuance** — `issued_at <= now`
6. **Time window** — `time_window_start <= now <= time_window_end`
7. **Scope** — action in `consent_scope`
8. **Endpoint** — if specified in constraints
9. **Merchant** — if specified in constraints
10. **Amount** — within `max_amount`
11. **Currency** — matches if specified
12. **Nonce** — not seen before (replay check)
13. **Offline** — freshness window OK if offline mode

Returns:

```typescript
interface VerificationResult {
  decision: "YES" | "NO";
  reason: string;
  checks: {
    signature_valid: boolean;
    not_expired: boolean;
    scope_valid: boolean;
    constraints_valid: boolean;
    replay_detected: boolean;
    offline_status: "ONLINE_VERIFIED" | "VERIFIED_OFFLINE" | "NEEDS_RECONNECT" | "REJECTED_OFFLINE";
  };
}
```

## API Endpoints

### POST /api/receipts/generate

Create and sign a consent receipt.

**Request:**
```json
{
  "agent_id": "shopping_agent_v1",
  "delegator_id": "user_12345",
  "consent_scope": ["pay", "book"],
  "max_amount": 500,
  "currency": "GBP",
  "merchant_id": "merchant_456",
  "ttl": 86400,
  "offline_policy": {
    "allowed": true,
    "freshness_seconds": 3600
  }
}
```

**Response:**
```json
{
  "success": true,
  "receipt": {
    "payload": { ... },
    "signature": "base64url...",
    "public_key": "base64url..."
  }
}
```

### POST /api/receipts/verify

Verify receipt against transaction context.

**Request:**
```json
{
  "receipt": { ... signed receipt ... },
  "context": {
    "action": "pay",
    "amount": 250,
    "currency": "GBP",
    "merchant_id": "merchant_456",
    "offline": false
  }
}
```

**Response:**
```json
{
  "success": true,
  "result": {
    "decision": "YES",
    "reason": "Consent receipt verified successfully",
    "checks": { ... }
  }
}
```

### POST /api/receipts/inspect

Decode receipt claims (no signature verification).

**Request:**
```json
{
  "receipt": { ... signed receipt ... }
}
```

**Response:**
```json
{
  "success": true,
  "inspection": {
    "receipt_id": "...",
    "agent_id": "...",
    "delegator_id": "...",
    "consent_scope": [...],
    "issued_at": "...",
    "expiry": "...",
    "constraints": { ... }
  }
}
```

### GET /api/health

Health check.

**Response:**
```json
{
  "status": "ok",
  "timestamp": "2026-03-18T10:00:00Z",
  "version": "1.0.0"
}
```

### GET /api/public-keys/:keyId

Get public key for signature verification.

**Response:**
```json
{
  "key_id": "demo",
  "public_key": "base64url...",
  "algorithm": "ed25519"
}
```

## CLI Commands

### generate-receipt

```bash
npm run cli -- generate-receipt \
  --agent <id> \
  --delegator <id> \
  --scope <scopes> \
  --max-amount <number> \
  --currency <code> \
  --merchant <id> \
  --ttl <seconds> \
  --secret-key <key> \
  --output <file>
```

### verify-receipt

```bash
npm run cli -- verify-receipt \
  --file <path> \
  --action <action> \
  --amount <number> \
  --currency <code> \
  --merchant <id> \
  --offline
```

### inspect-receipt

```bash
npm run cli -- inspect-receipt --file <path>
```

### generate-keys

```bash
npm run cli -- generate-keys [--output <dir>]
```

## Replay Protection

ACRRV prevents replay attacks by tracking nonces. Two implementations are provided:

### InMemoryReplayStore

Fast, in-memory tracking. Suitable for single-instance servers.

```typescript
import { InMemoryReplayStore } from './core/replay';

const store = new InMemoryReplayStore();
const verifier = new Verifier(store);
```

### FileReplayStore

Persistent JSON file. Suitable for development and small deployments.

```typescript
import { FileReplayStore } from './core/replay';

const store = new FileReplayStore('./.acrrv-nonces.json');
const verifier = new Verifier(store);
```

### Custom Adapter

Implement the `ReplayStore` interface to use Redis, Postgres, or any KV store:

```typescript
export interface ReplayStore {
  hasNonce(nonce: string): Promise<boolean>;
  markNonce(nonce: string, receiptId: string, expiry: Date): Promise<void>;
  pruneExpired(): Promise<void>;
}
```

## Offline Verification

Agents can operate offline and verify receipts locally using only the signature check. Three states are possible:

- **ONLINE_VERIFIED** — Authoritative check via API, nonce recorded
- **VERIFIED_OFFLINE** — Signature valid, constraints met, nonce unseen locally, within freshness window
- **NEEDS_RECONNECT** — Signature valid but freshness window expired or nonce was seen before
- **REJECTED_OFFLINE** — Signature invalid or constraints violated

### Example

```typescript
const context: VerificationContext = {
  action: 'pay',
  amount: 250,
  currency: 'GBP',
  merchant_id: 'merchant_456',
  offline: true,  // ← offline mode
  lastOnlineTimestamp: new Date('2026-03-18T09:00:00Z'),
};

const result = await verifier.verify(signedReceipt, context);
// result.checks.offline_status === "VERIFIED_OFFLINE"
```

## Security Considerations

See [SECURITY.md](./SECURITY.md) for detailed security analysis, threat model, and deployment recommendations.

## Development

### Build

```bash
npm run build
```

### Dev Mode

```bash
npm run dev
```

### Test

```bash
npm test
```

## License

MIT

## Support

For questions or issues, contact **hello@affix-io.com** or visit [https://www.affix-io.com/lab/agentic-consent-receipt/](https://www.affix-io.com/lab/agentic-consent-receipt/)

---

**ACRRV v1.0 — Proof-based consent for autonomous agents.**
