Skip to content

zk-wavescout/zk-wavescout-coordinator

Repository files navigation

zk-wavescout-coordinator

The Oracles & Payload Relayer module of ZK-WaveScout — a zero-knowledge-powered automated bounty validation engine for the Soroban ecosystem. It bridges Stellar on-chain events to GitHub pull requests, automatically decrypting and merging verified contributor solutions.


System Topology

                                    [ Maintainer ]
                                          │
                     Creates Challenge    │ (Posts encrypted solution to Coordinator DB)
                     & Funds Bounty       ▼
                 ┌─────────────────────────────────┐
                 │      Soroban Smart Contract      │
                 └────────────────┬────────────────┘
                                  ▲
                                  │ Invokes claim_bounty() with ZK Proof
                                  │
  ┌─────────────────┐  Generates  │  ┌─────────────────────────────────┐
  │  Noir Circuit   ├─────────────┼──┤      Contributor Client         │
  │  (Off-chain)    │  ZK Proof   │  │       (Frontend dApp)           │
  └─────────────────┘             │  └────────────────┬────────────────┘
                                  │                   │ POST /api/submissions/upload
                                  │                   ▼
  ┌─────────────────┐             │  ┌─────────────────────────────────┐
  │ GitHub Repo     │◄────────────┼──┤      zk-wavescout-coordinator   │
  │ (Automated PR)  │  Opens PR   │  │      (this module)              │
  └─────────────────┘  on decrypt │  └─────────────────────────────────┘
                                  ▼
                       [ bounty_claimed Event ]

Module Architecture

Four Sub-Modules

Module Role
zk-wavescout-circuits Noir ZK circuit — proves knowledge of secret solution $s$ s.t. $H = \text{Poseidon}(s)$ without revealing $s$
zk-wavescout-contracts Soroban smart contract — escrow vault, proof verifier, payout dispatcher, event emitter
zk-wavescout-coordinator This module — Stellar event listener, AES-GCM decryptor, GitHub PR opener
zk-wavescout-frontend Browser dApp — in-browser Noir WASM proving, Stellar RPC queries, solution upload

Integration Lifecycle

[1. Challenge Setup]
Maintainer calls create_challenge() ──► Soroban Contract stores H = Poseidon(s)
                                   └──► POST /api/submissions/upload (encrypted solution E)

[2. Proving Phase]
Contributor writes solution s ──► Frontend WASM Prover ──► ZK Proof + public inputs

[3. On-Chain Claim]
Frontend calls claim_bounty(proof) ──► Contract verifies ──► Releases USDC ──► Emits bounty_claimed

[4. Automation & Merge]
Coordinator streams Stellar events ──► Decrypts E with revealed key K ──► Opens GitHub PR

Directory Structure

zk-wavescout-coordinator/
├── .env.example
├── package.json
├── tsconfig.json
└── src/
    ├── index.ts      # Express server + decryption orchestrator
    ├── stellar.ts    # Horizon event stream listener
    ├── github.ts     # Octokit PR automation
    └── types.ts      # Shared interfaces

Cryptographic Decryption Pipeline

The contributor encrypts their solution $C$ client-side before the on-chain claim:

$$E = \text{AES-GCM-Encrypt}(C,; K)$$

$E$, the IV, and the auth tag are uploaded to /api/submissions/upload. $K$ is revealed only after the Soroban contract emits bounty_claimed. The coordinator then:

  1. Derives the AES key: scryptSync(K, KDF_SALT, 32)
  2. Decrypts $E$ with the stored IV and auth tag
  3. Calls the GitHub API to commit the plaintext and open a PR

Security: Wallet Binding

The Noir circuit includes the contributor's Stellar address as a public input constraint:

$$H == \text{Poseidon}(s) ;\land; \text{Dummy} == \text{ContributorWallet}$$

This binds the proof to the sender's key. Any replay or front-run attempt with a different address fails on-chain verification.


Bug Fixes (vs. original scaffold)

# Bug Fix
1 Horizon.Server URL contained markdown link syntax: '[https://...](https://...)' Use plain string 'https://horizon-testnet.stellar.org'
2 scryptSync(secret, 'salt', 32) — hardcoded literal 'salt' is cryptographically weak Salt is now read from KDF_SALT env var as a Buffer.from(salt, 'hex')
3 setAuthTag received the raw payload.authTag string instead of a Buffer Wrapped with Buffer.from(payload.authTag, 'hex')
4 dotenv was never loaded — process.env vars were always undefined Added import 'dotenv/config' at the top of index.ts
5 @octokit/rest was missing from package.json dependencies Added "@octokit/rest": "^20.0.2"
6 Stellar event listener was a no-op stub Replaced with real horizonServer.transactions().forAccount().cursor('now').stream() call in stellar.ts

Setup

cp .env.example .env
# Fill in CONTRACT_ADDRESS, KDF_SALT, GITHUB_TOKEN, GITHUB_OWNER, GITHUB_REPO

npm install
npm run build
npm start

Generate a KDF_SALT

openssl rand -hex 32

API

POST /api/submissions/upload

Buffers an encrypted solution before the on-chain claim is finalised.

Body:

{
  "contributorAddress": "GXXXX...",
  "encryptedCode": "<base64 ciphertext>",
  "iv": "<32 hex chars — 16 bytes>",
  "authTag": "<32 hex chars — 16 bytes>"
}

Response:

{ "status": "encrypted_payload_buffered" }

Environment Variables

Variable Description
PORT HTTP server port (default 5000)
CONTRACT_ADDRESS Soroban contract's Stellar account address
KDF_SALT 64-char hex salt for scryptSync key derivation
GITHUB_TOKEN GitHub personal access token with repo scope
GITHUB_OWNER GitHub org or username
GITHUB_REPO Target repository name
GITHUB_BASE_BRANCH Branch to open PRs against (default main)

About

A simple dashboard for contributors to view active puzzles, generate Noir proofs locally using their hardware, and submit the proof to claim their rewards.

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors