npm package: @riprip-io/provably-fair
Description: RipRip provably-fair library
Latest version: 0.2.0

# @riprip-io/provably-fair

Isomorphic TypeScript implementation of the OPENv1 provably-fair RNG protocol. Used by the API (payment indexer, pack opening resolution), the admintool (verification tool, simulator), and the future proof verification page.

**Protocol spec:** [`docs/rng-v1.md`](../../docs/rng-v1.md) — defines the cryptographic derivations, message format, and weighted selection algorithm.

**Security audit:** [`docs/audit-provably-fair-v0.2.md`](../../docs/audit-provably-fair-v0.2.md) — cryptographic soundness review, fairness analysis, and hardening report.

## Install

```bash
cd packages/provably-fair
npm install
```

Single runtime dependency: `@noble/hashes` (pure JS, audited, isomorphic).

## API

### Derivations

```typescript
import { deriveUserKey, deriveClientSeedHash } from '@riprip-io/provably-fair';

// user_key = SHA256("userkey:v1" || uuid_16_bytes)
const userKey: Uint8Array = deriveUserKey('550e8400-e29b-41d4-a716-446655440000');

// client_seed_hash = SHA256("clientseed:v1" || utf8(seed))
const seedHash: Uint8Array = deriveClientSeedHash('my-custom-seed');
```

### RNG Resolution

```typescript
import { resolveOpen, resolveOpenBatch } from '@riprip-io/provably-fair';
import type { DrawTable } from '@riprip-io/provably-fair';

const drawTables: DrawTable[] = [
  {
    drawId: 1,
    drawsPerOpen: 1,
    items: [
      { sku: 'v1:card-a:raw:none:EN', weight: 700 },
      { sku: 'v1:card-b:raw:none:EN', weight: 300 },
    ],
  },
];

// Resolve a single pack opening
const result = resolveOpen(
  serverSecret,      // Uint8Array (32 bytes, from epoch reveal)
  20260215,          // epochId (YYYYMMDD)
  userKey,           // Uint8Array (32 bytes)
  1n,                // purchaseNonce (bigint)
  packConfigHash,    // Uint8Array (32 bytes)
  0,                 // openIndex
  clientSeedHash,    // Uint8Array (32 bytes)
  drawTables,
);
// → { openIndex: 0, draws: [{ openIndex, drawIndex, ticket, selectedItem, rejectionSamples }] }

// Resolve a batch (multiple packs from one purchase)
const batch = resolveOpenBatch(
  serverSecret, epochId, userKey, purchaseNonce,
  packConfigHash, clientSeedHash, quantityN, drawTables,
);
// → { results: OpenResult[] }
```

### Epoch Commitment

```typescript
import { commitEpoch, verifyEpoch } from '@riprip-io/provably-fair';

// commit_hash = SHA256(serverSecret)
const commitHash: Uint8Array = commitEpoch(serverSecret);

// Verify: SHA256(revealedSecret) === publishedCommitHash
const isValid: boolean = verifyEpoch(revealedSecret, publishedCommitHash);
```

### Lower-Level Primitives

```typescript
import { buildMessage, generateTicket, selectWeighted } from '@riprip-io/provably-fair';

// Build the 122-byte OPENv1 message
const message: Uint8Array = buildMessage(drawInput);

// Generate a ticket (0–999999) from HMAC-SHA256
const ticket: number = generateTicket(serverSecret, message);

// Bias-free weighted selection with rejection sampling
const result = selectWeighted(serverSecret, message, items);
// → { sku: string, retries: number }
```

## Types

| Type | Description |
|------|-------------|
| `DrawInput` | Inputs for one draw: epochId, userKey, purchaseNonce, packConfigHash, openIndex, drawIndex, clientSeedHash |
| `WeightedItem` | `{ sku: string, weight: number }` |
| `DrawTable` | `{ drawId, drawsPerOpen, items: WeightedItem[] }` |
| `DrawResult` | Result of one draw: openIndex, drawIndex, ticket, selectedItem, rejectionSamples |
| `OpenResult` | `{ openIndex, draws: DrawResult[] }` |
| `OpenBatchResult` | `{ results: OpenResult[] }` |
| `EpochCommitment` | `{ epochId, commitHash }` |

## How It Fits Together

```
Contract (on-chain)          This package              API (off-chain)
───────────────────          ────────────              ───────────────
OrderPaidV1 event    ──→     resolveOpenBatch()   ──→  packOpening +
  clientSeedHash             deriveUserKey()           packOpeningResult
  purchaseNonce              deriveClientSeedHash()    rows in DB
  packConfigHash
  epochId                    Epoch service
                             commitEpoch()        ──→  epoch table
                             verifyEpoch()             (commit/reveal)
```

## Commands

| Command | Description |
|---------|-------------|
| `npm run build` | Compile TypeScript |
| `npm run test` | Run tests (vitest, watch mode) |
| `npm run test:run` | Run tests once |
| `npm run test:coverage` | Run tests with coverage |
| `npm run typecheck` | `tsc --noEmit` |

Tests include frozen golden test vectors and adversarial edge-case coverage across 8 files.