Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ target
.DS_Store
ivc
node_modules
.env
6 changes: 6 additions & 0 deletions prediction-market-zktls/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Primus zkTLS API credentials — get from https://dev.primuslabs.xyz
PRIMUS_APP_ID=
PRIMUS_APP_SECRET=

# Aztec node URL (defaults to http://localhost:8080)
# AZTEC_NODE_URL=http://localhost:8080
7 changes: 7 additions & 0 deletions prediction-market-zktls/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.env
node_modules/
store/
target/
artifacts/
codegenCache.json
*.json.bak
72 changes: 72 additions & 0 deletions prediction-market-zktls/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# CLAUDE.md

## Project Overview

Private prediction market on Aztec with zkTLS resolution and real token collateral. Binary outcome markets ("Will BTC be above $X by date Y?") using a **complete-set model** for provable solvency.

## Architecture

Uses a complete-set model instead of an AMM:
- `mint_sets(n)`: deposit n collateral tokens, receive n YES + n NO shares
- `burn_sets(n)`: return n YES + n NO shares, receive n collateral tokens
- `redeem(n)`: after resolution, burn n winning shares, receive n collateral
- Solvency invariant: total_collateral = total_yes = total_no (always)

### Files
- **src/main.nr**: `PredictionMarketZkTLS` contract -- complete sets, token integration, zkTLS resolution, private redemption
- **src/config.nr**: Constants (NUM_ALLOWED_URLS)
- **src/price.nr**: ASCII decimal price parser (e.g., "97234.56" -> 9723456 cents)
- **scripts/**: TypeScript for attestation parsing, URL hashing, deployment
- **tests/**: Integration tests using vitest

## Key Design Decisions

- **Complete-set model** instead of AMM -- provably solvent, no insolvency risk
- **Real token collateral** via defi-wonderland/aztec-standards Token contract
- `mint_sets` uses `transfer_private_to_public` (user -> contract public balance) with auth witness
- `burn_sets`/`redeem` use `transfer_public_to_private` (contract -> user private balance)
- `resolve_market` is **private** (ECDSA verification in circuit), enqueues public `_set_resolution`
- **Trusted attester pinning**: Poseidon2 hash of attester's public key stored at deployment; `resolve_market` rejects attestations from unknown signers
- **MPC TLS mode** (`mpctls`): client and attester collaboratively compute attestation (neither sees full TLS key material)
- **Resolution window** (7 days) limits stale attestation attacks
- Double-resolution prevention via `PublicImmutable::initialize`

## Token Integration

Collateral flow:
- Deposit: `Token.transfer_private_to_public(user, market, amount, nonce)` -- requires auth witness
- Withdrawal: `Token.transfer_public_to_private(market, user, amount, 0)` -- no auth witness (market is `from`)

Deployment order:
1. Parse attestation file to extract attester public key, compute Poseidon2 key hash
2. Deploy PredictionMarketZkTLS (pass `attester_key_hash` to constructor)
3. Deploy Token (with `constructor_with_minter`, admin as minter)
4. Call `market.set_token(token.address)`
5. Mint tokens to users via `token.mint_to_private(recipient, amount)`

## Development Commands

```bash
yarn install # Install dependencies
yarn ccc # Compile contract + generate TypeScript bindings
yarn test:noir # Run Noir unit tests (price parser)
yarn generate # Generate zkTLS price attestation
yarn demo # Full lifecycle demo
yarn test # Run integration tests
```

## Contract Constants

Must stay in sync between contract and TypeScript:
- `MAX_URL_LEN = 128`
- `MAX_PLAINTEXT_LEN = 50`
- `NUM_RESPONSE_RESOLVE = 2`
- `NUM_ALLOWED_URLS = 3`
- `RESOLUTION_WINDOW = 604800` (7 days in seconds)

## Dependencies

- Aztec: `v4.2.0-aztecnr-rc.2`
- Token: `defi-wonderland/aztec-standards` at `v4.2.0-aztecnr-rc.2`
- `att_verifier_lib`: from `primus-labs/zktls-verification-noir` (main)
- `poseidon` v0.2.6
12 changes: 12 additions & 0 deletions prediction-market-zktls/Nargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "prediction_market_zktls_contract"
type = "contract"
authors = [""]

[dependencies]
aztec = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/aztec" }
uint_note = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/uint-note" }
balance_set = { git = "https://github.com/AztecProtocol/aztec-packages/", tag = "v4.2.0-aztecnr-rc.2", directory = "noir-projects/aztec-nr/balance-set" }
poseidon = { git = "https://github.com/noir-lang/poseidon", tag = "v0.2.6" }
att_verifier_lib = { git = "https://github.com/primus-labs/zktls-verification-noir", tag = "main", directory = "att_verifier_lib" }
token_contract = { git = "https://github.com/defi-wonderland/aztec-standards", tag = "v4.2.0-aztecnr-rc.2", directory = "src/token_contract" }
145 changes: 145 additions & 0 deletions prediction-market-zktls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Private Prediction Market with zkTLS Resolution

A private prediction market on Aztec with real token collateral, provable solvency via complete sets, and trustless resolution via zkTLS.

## What This Does

- **Private positions**: No one sees who holds YES or NO shares
- **Real collateral**: Backed by actual Aztec token transfers (not phantom balances)
- **Provably solvent**: Complete-set model guarantees total collateral >= total winning shares
- **Trustless resolution**: Primus zkTLS attestation from CoinGecko's price API
- **Private settlement**: Winners redeem shares for collateral tokens

## Complete-Set Model

Unlike AMM-based prediction markets that can become insolvent, this contract uses **complete sets**:

```
mint_sets(1000): deposit 1000 collateral -> get 1000 YES + 1000 NO shares
burn_sets(1000): return 1000 YES + 1000 NO -> get 1000 collateral back
redeem(1000): after resolution, burn 1000 winning shares -> get 1000 collateral
```

**Solvency proof**: Every collateral token backs exactly 1 YES + 1 NO share. After resolution, only winning shares redeem. Since `winning_shares <= total_shares = total_collateral`, the contract is always solvent.

Users trade YES and NO shares peer-to-peer. The market price emerges from what traders are willing to pay for each side.

## How It Works

```
1. DEPLOY
Admin creates market: "BTC above $50k by July 1?"
Sets: threshold, expiry, CoinGecko URL hash, collateral token,
trusted attester key hash (Poseidon2 of Primus public key)

2. MINT SETS (private)
Users deposit collateral tokens -> receive equal YES + NO shares
Auth witness authorizes the token transfer

3. TRADE (off-chain / peer-to-peer)
Users trade YES/NO shares to express their view
Can also burn_sets() to exit entirely (return YES+NO for collateral)

4. RESOLUTION (after expiry, within 7-day window)
Anyone submits CoinGecko zkTLS attestation (MPC-TLS mode)
Private: ECDSA verification + attester identity check + price parsing
Public: expiry check + resolution window + state update

5. SETTLEMENT (private)
Winners: redeem(amount) -> burn shares, receive collateral tokens
Losers: shares are worthless (or burn complete sets if holding both)
```

## Quick Start

```bash
# Install Aztec tools
bash -i <(curl -s https://install.aztec.network)
aztec-up 4.2.0-aztecnr-rc.2

# Install dependencies
yarn install

# Compile contract + generate TypeScript bindings
yarn ccc

# Run Noir unit tests
yarn test:noir

# Generate price attestation (needs PRIMUS credentials in .env)
yarn generate

# Start Aztec local network (separate terminal)
aztec start --local-network

# Run full lifecycle demo
yarn demo

# Run integration tests
yarn test
```

## Architecture

### Contract (`src/main.nr`)

**Storage:**
- `yes_balances`, `no_balances` -- private share notes (Owned<BalanceSet>)
- `token` -- collateral token address (PublicImmutable)
- `total_sets` -- total complete sets outstanding (PublicMutable)
- `trusted_attester_hash` -- Poseidon2 hash of trusted Primus attester public key (PublicImmutable)
- `expiry`, `price_threshold`, `threshold_above`, `allowed_url_hashes` -- market config (PublicImmutable)
- `resolution_outcome`, `resolution_price` -- result (PublicImmutable, initialized once)

**Key Functions:**
| Function | Context | Purpose |
|----------|---------|---------|
| `mint_sets(amount, nonce)` | private | Deposit collateral, get YES+NO shares |
| `burn_sets(amount)` | private | Return YES+NO shares, get collateral back |
| `redeem(amount)` | private | After resolution, burn winning shares for collateral |
| `resolve_market(...)` | private->public | Verify zkTLS attestation, set outcome |
| `set_token(addr)` | public | One-time token link (admin only) |

### Token Integration

Uses [defi-wonderland/aztec-standards](https://github.com/defi-wonderland/aztec-standards) Token contract at `v4.2.0-aztecnr-rc.2`.

- **Deposit**: `Token.transfer_private_to_public(user, market, amount, nonce)` with auth witness
- **Withdrawal**: `Token.transfer_public_to_private(market, user, amount, 0)` -- no auth needed

### zkTLS Resolution

Uses [Primus zkTLS](https://primuslabs.xyz) in **MPC-TLS mode** (`mpctls`): the client and Primus attester collaboratively compute the TLS session key material via multi-party computation. Neither party holds the full key alone, so neither can unilaterally forge TLS data.

After market expiry (within 7-day resolution window):
1. Anyone fetches BTC price from CoinGecko via Primus zkTLS (MPC-TLS mode)
2. `resolve_market` (private) verifies ECDSA signature, checks attester identity against pinned key hash, parses price
3. `_set_resolution` (public) checks expiry window and sets outcome (`PublicImmutable::initialize` prevents double-resolution)

**Trust assumptions:**
- **Primus attester identity** is pinned at deployment (Poseidon2 hash of the attester's secp256k1 public key stored in contract). Attestations from unknown signers are rejected.
- **MPC-TLS** prevents either party (client or attester) from forging TLS data unilaterally. In contrast, proxy-TLS mode trusts the attester to actually communicate with the intended server.
- **CoinGecko** is trusted as the price data source. The contract whitelists allowed API URLs via Poseidon2 hashes.
- **Resolution window** (7 days) limits the use of stale attestations.

## Project Structure

```
prediction-market-zktls/
|-- src/
| |-- main.nr # Contract: complete sets, token integration, resolution
| |-- config.nr # Constants
| +-- price.nr # ASCII price parser + unit tests
|-- scripts/
| |-- parse_attestation.ts # Attestation -> contract args
| |-- compute_url_hashes.ts # Poseidon2 URL + attester key hashing
| |-- generate_attestation.ts # CoinGecko attestation generator
| |-- deploy_and_resolve.ts # Full lifecycle demo
| +-- sponsored_fpc.ts # Fee payment helper
|-- tests/
| +-- prediction_market_zktls.test.ts
|-- testdata/
| +-- sample-attestation.json
|-- Nargo.toml
+-- package.json
```
36 changes: 36 additions & 0 deletions prediction-market-zktls/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"name": "prediction-market-zktls",
"description": "Private prediction market on Aztec with zkTLS resolution via Primus attestations from CoinGecko",
"type": "module",
"scripts": {
"clean": "rm -rf store target artifacts codegenCache.json",
"ccc": "aztec compile && aztec codegen target -o artifacts",
"url-hashes": "tsx scripts/compute_url_hashes.ts",
"generate": "tsx scripts/generate_attestation.ts",
"demo": "tsx scripts/deploy_and_resolve.ts",
"test": "yarn clean && vitest run",
"test:watch": "vitest",
"test:noir": "aztec test"
},
"devDependencies": {
"@types/node": "^22.0.0",
"vitest": "^3.0.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@aztec/accounts": "4.2.0-aztecnr-rc.2",
"@aztec/aztec.js": "4.2.0-aztecnr-rc.2",
"@aztec/bb.js": "4.2.0-aztecnr-rc.2",
"@aztec/kv-store": "4.2.0-aztecnr-rc.2",
"@aztec/noir-contracts.js": "4.2.0-aztecnr-rc.2",
"@aztec/pxe": "4.2.0-aztecnr-rc.2",
"@aztec/wallets": "4.2.0-aztecnr-rc.2",
"@noble/curves": "^1.8.0",
"@noble/hashes": "^1.7.0",
"@primuslabs/zktls-core-sdk": "^0.2.7",
"dotenv": "^17.3.1",
"tsx": "^4.20.6"
}
}
26 changes: 26 additions & 0 deletions prediction-market-zktls/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

echo "=== Prediction Market zkTLS - Test Suite ==="
echo ""

# 1. Compile contract
echo "Step 1: Compiling contract..."
aztec compile
aztec codegen target -o artifacts
echo " Done."
echo ""

# 2. Run Noir unit tests (CSMM pricing + price parser)
echo "Step 2: Running Noir unit tests..."
aztec test
echo " Done."
echo ""

# 3. Run integration tests (requires local network)
echo "Step 3: Running integration tests..."
echo " (Ensure Aztec local network is running: aztec start --local-network)"
yarn test
echo ""

echo "=== All tests complete ==="
Loading