# @rolly-dev/wasm-signer

Client-side Poseidon2 hashing and bet authentication for the Rolly ZK-Rollup casino.

Built with Rust → WebAssembly (via `wasm-pack`), using the same Poseidon2 hasher
and Goldilocks field as the plonky2 proving circuit.

## Build

```bash
# requires: cargo, wasm-pack
npm run build
# → dist/node/  (Node.js, CJS, sync init)
# → dist/web/   (Browser, ESM, async init)
```

## Usage

### Node.js (CommonJS)

```javascript
const {
  poseidon2_hash,
  derive_session_key,
  session_public_key,
} = require('@rolly-dev/wasm-signer');

const hash = poseidon2_hash(BigUint64Array.from([1n, 2n, 3n]));
// → BigUint64Array(4)
```

### Node.js (ESM)

```javascript
import {
  poseidon2_hash,
  derive_session_key,
  session_public_key,
} from '@rolly-dev/wasm-signer';

const sessionKey = derive_session_key(metamaskSignatureBytes);
const pkHash = session_public_key(sessionKey, BigInt(expiryTimestamp));
```

### React

```jsx
import { useRollyWasm } from '@rolly-dev/wasm-signer/react';

function SessionInfo({ sessionKey, expiry }) {
  const { ready, session_public_key } = useRollyWasm();

  if (!ready) return <span>Loading...</span>;

  const pkHash = session_public_key(sessionKey, BigInt(expiry));
  // register pkHash on-chain via key_register tx...

  return <div>Session active until {new Date(Number(expiry) * 1000).toLocaleString()}</div>;
}
```

### Browser (vanilla ESM)

```html
<script type="module">
  import { init, poseidon2_hash } from '@rolly-dev/wasm-signer';

  await init();  // loads .wasm, must be called once
  const h = poseidon2_hash(BigUint64Array.from([1n, 2n]));
</script>
```

### Manual init (advanced)

```javascript
import init, { poseidon2_hash } from '@rolly-dev/wasm-signer/init';

// custom wasm URL or ArrayBuffer
await init('/assets/rolly_wasm_signer_bg.wasm');
poseidon2_hash(BigUint64Array.from([1n]));
```

## API

| Function                   | Input                          | Output            | Description                                    |
|----------------------------|--------------------------------|-------------------|------------------------------------------------|
| `poseidon2_hash`           | `BigUint64Array`               | `BigUint64Array(4)` | Hash N field elements                        |
| `poseidon2_two_to_one`     | `BigUint64Array(4)` × 2        | `BigUint64Array(4)` | Merkle hash: H(left‖right)                   |
| `derive_session_key`       | `Uint8Array(32)`               | `BigUint64Array(4)` | MetaMask sig → session key                   |
| `session_public_key`       | `(BigUint64Array(4), bigint)` | `BigUint64Array(4)` | pk_hash = Poseidon2(session_key, expiry)       |
| `compute_server_seed_hash` | `BigUint64Array(8)`            | `BigUint64Array(4)` | Full hash of server seed                     |
| `seed_hash_truncated`      | `BigUint64Array(8)`            | `BigUint64Array(3)` | First 3 elements (circuit leaf format, 192-bit commitment) |
| `goldilocks_modulus`       | —                              | `bigint`          | Returns p = 2^64 - 2^32 + 1                   |
| `goldilocks_reduce`        | `bigint`                       | `bigint`          | Reduce mod p                                   |

## Bundler notes

**Vite** — add to `optimizeDeps.exclude`:
```js
optimizeDeps: { exclude: ['@rolly-dev/wasm-signer'] }
```

**Webpack 5** — enable WASM experiments:
```js
module.exports = { experiments: { asyncWebAssembly: true } };
```

**Next.js** — conditional exports auto-resolve: Node.js entry on server, browser entry on client.

## Dice — range constraints

Roll number: `[0, 999]` (TOTAL_RANGE = 1000).
win_numbers (wn): `[1, 950]` (MIN_RANGE..=MAX_RANGE).

### prediction_range constraints per mode

| Mode  | wn formula                                     | prediction\[0\] | prediction\[1\] | wn range   |
|-------|-------------------------------------------------|-----------------|-----------------|------------|
| Under | `prediction[0]`                                 | `[1, 950]`      | unused (0)      | `[1, 950]` |
| Over  | `999 − prediction[0]`                           | `[49, 998]`     | unused (0)      | `[1, 950]` |
| In    | `prediction[1] − prediction[0] + 1`             | `[0, 999]`      | `[0, 999]`      | `[1, 950]` |
| Out   | `1000 − (prediction[1] − prediction[0] + 1)`    | `[0, 999]`      | `[0, 999]`      | `[1, 950]` |

**Under** — `prediction[0]` ∈ `[1, 950]` (= wn directly). Win: `roll < prediction[0]`.

**Over** — `prediction[0]` ∈ `[49, 998]` (lower: `999 − 950 = 49`, upper: `999 − 1 = 998`). Win: `roll > prediction[0]`.

**In** — `prediction[0] <= prediction[1]`, span ∈ `[1, 950]`. Win: `prediction[0] <= roll <= prediction[1]`.

**Out** — `prediction[0] <= prediction[1]`, inner span ∈ `[50, 999]` (wn = `1000 − inner` ∈ `[1, 950]`). Win: `roll < prediction[0] || roll > prediction[1]`.

### Multiplier

`multiplier = floor(9_900_000 / wn)` (scaled ×10000).

| wn  | multiplier | display   |
|-----|-----------|-----------|
| 1   | 9_900_000 | 990.0000x |
| 500 | 19_800    | 1.9800x   |
| 950 | 10_421    | 1.0421x   |

### RTP

Theoretical: **98.998%** (floor-division costs ~0.002%). House edge: **~1.002%**. Max win cap: 10,000 USDT (business rule, not formula).

## License

MIT
