Skip to content
Closed
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
48 changes: 41 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ jobs:
name: SDK + relay (build & test)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
# Version is inferred from the root package.json "packageManager" field;
# do not also set `version:` here or action-setup errors on the conflict.
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
cache: pnpm
Expand All @@ -29,7 +29,7 @@ jobs:
name: xah-did hook (wasm32 compile)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install clang + lld
run: sudo apt-get update && sudo apt-get install -y clang lld
- run: bash hooks/xah-did/build.sh
Expand All @@ -40,7 +40,41 @@ jobs:
name: Anchor programs (cargo check)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: ". -> target"
- run: cargo check --workspace
- run: cargo test -p poi-subscription

zk-circuits:
name: ZK circuits (manifest validation)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
# sp1-sdk 3.4.0 is incompatible with every current stable Rust toolchain:
# Rust <1.82: E0283 type-inference errors in sp1-core-machine
# Rust <1.85: transitive dep cpufeatures 0.3.0 requires edition2024
# Full compilation requires a pinned Cargo.lock generated with the sp1
# custom toolchain. Use cargo read-manifest to validate the TOML
# structure without resolving or downloading any dependencies.
- name: Validate workspace manifests
run: |
# packages/zk-circuits/Cargo.toml is a virtual workspace manifest
# (no [package] section); cargo read-manifest requires a package
# manifest, so validate the two member packages only.
cargo read-manifest --manifest-path packages/zk-circuits/program/Cargo.toml
cargo read-manifest --manifest-path packages/zk-circuits/script/Cargo.toml

circuits:
name: ZK provenance reference (JS)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
- run: cd circuits && npm install
- run: cd circuits && npm test
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ test-ledger/
hooks/**/*.o
hooks/**/*.wasm

# ZK circuit build artifacts (regenerated by circuits/build.sh)
circuits/*.r1cs
circuits/*.sym
circuits/*.ptau
circuits/*.zkey
circuits/*_js/
circuits/verification_key.json
circuits/verifier.sol

# Env / secrets — NEVER commit keys or seeds (zero-custody: no treasury keys in repo)
.env
.env.*
Expand Down
2 changes: 2 additions & 0 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ skip-lint = false
[programs.localnet]
poi_gossip = "5ycmzEXUYMx4uRVu4hLqqNXRMzWhUu7KvMVeDfECE9o1"
poi_escrow = "G41hFoSfYJ6ETtvxVayZtFx4oUVwWY7ctsgUB1BQBtPH"
poi_subscription = "9MeEYCFExtHAFiXa4ZFmW4nh34n3mk2hyqm91jDwSbEE"
poi_verifier = "Verif1er11111111111111111111111111111111111"

[programs.devnet]
poi_gossip = "5ycmzEXUYMx4uRVu4hLqqNXRMzWhUu7KvMVeDfECE9o1"
poi_escrow = "G41hFoSfYJ6ETtvxVayZtFx4oUVwWY7ctsgUB1BQBtPH"
poi_subscription = "9MeEYCFExtHAFiXa4ZFmW4nh34n3mk2hyqm91jDwSbEE"
poi_verifier = "Verif1er11111111111111111111111111111111111"

[registry]
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ treasury is SOL SaaS revenue (§3.1).
zeroquery-protocol/
├── programs/
│ ├── poi-gossip/ # Anchor/Rust — L1 intent broadcast + Intent Dust event
│ └── poi-escrow/ # Anchor/Rust — L3 non-custodial USDC intent bonds (x402)
│ ├── poi-escrow/ # Anchor/Rust — L3 non-custodial USDC intent bonds (x402)
│ └── poi-subscription/ # Anchor/Rust — SOL SaaS tier management (Scout/Runner/Relay/Builder)
├── hooks/xah-did/ # Xahau Hook (C → wasm32) — DID resolution + soulbound reputation
├── packages/
│ ├── sdk/ # @zeroquery/sdk — DID, intent gossip, dust, resolver
Expand All @@ -59,18 +60,22 @@ zeroquery-protocol/

| Deliverable | State | Verified in-repo |
|-------------|-------|------------------|
| `@zeroquery/sdk` — DID resolution, intent gossip, Intent Dust | ✅ working | 27 tests passing |
| `@zeroquery/sdk` — DID, intent gossip, Intent Dust, IntentRank | ✅ working | 35 tests passing |
| `@zeroquery/relay` — open-source gossip node | ✅ working | 6 tests passing |
| Intent schema + canonical hashing + gossip message | ✅ working | covered by SDK tests |
| `xah-did` Hook (DID → soulbound reputation) | ✅ compiles to wasm32 | `pnpm hook:build` |
| `poi-gossip` Anchor program (L1 broadcast) | ✅ compiles | `cargo check --workspace` |
| `poi-escrow` Anchor program (L3 x402 USDC bonds) | ✅ compiles | `cargo check --workspace` |
| `poi-subscription` Anchor program (SOL SaaS tiers) | ✅ compiles + unit-tested | `cargo test -p poi-subscription` |
| IntentRank matching (L2, Phase 2) | ✅ working | in the SDK suite |
| ZK provenance scheme (Phase 2) | ✅ JS-verified | `cd circuits && npm test` |
| ZK Groth16 circuit + setup | ⏳ needs circom/snarkjs | `circuits/build.sh` |
| End-to-end Phase 1 flow | ✅ runs | `pnpm example` |
| ZK provenance circuits | ⬜ Phase 2 | — |
| Live Xahau-testnet / devnet deploy | ⬜ needs creds | runbook in `docs/DEPLOY.md` |

Full mapping of constraints → code in [`docs/COMPLIANCE.md`](docs/COMPLIANCE.md);
scope detail in [`docs/PHASE1.md`](docs/PHASE1.md).
Constraints → code in [`docs/COMPLIANCE.md`](docs/COMPLIANCE.md); scope in
[`docs/PHASE1.md`](docs/PHASE1.md) and [`docs/PHASE2.md`](docs/PHASE2.md);
self-review in [`docs/AUDIT.md`](docs/AUDIT.md).

---

Expand Down
53 changes: 53 additions & 0 deletions circuits/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# @zeroquery/circuits — ZK provenance

Zero-Knowledge attestation that a responder actually obtained data at a given
time, bound to its identity and to a specific intent — without revealing the raw
data or its private key. (spec §3.5 ZK Attestation Mandate, §4.5 ZK Provenance.)

## The scheme

Witness (private): `apiResponseHash`, `salt`, `privateKey`
Public inputs: `timestamp`, `intentHash`
Public outputs: `commitment`, `nullifier`

```
commitment = Poseidon(apiResponseHash, timestamp, salt)
nullifier = Poseidon(privateKey, intentHash)
```

- **commitment** — binds hidden data to a public timestamp. `salt` makes it
hiding; opening it later proves "I held this exact response at this time".
- **nullifier** — binds the proof to the responder's secret key and this intent.
A verifier that records spent nullifiers gets replay protection, and a
false-attestation **slash** (spec §3.5) targets the nullifier without ever
learning the key.

Poseidon (not SHA-256) is used because it is SNARK-friendly.

## What's verifiable here vs. what needs the toolchain

- ✅ **`reference.mjs` + `reference.test.mjs`** — a pure-JS implementation using
the *same* Poseidon hash the circuit constrains. Run `npm test` (or
`pnpm test`) to verify determinism, hiding, identity/intent binding, and
replay detectability. This is the off-chain attestation an agent posts with
its proof; its public signals match the circuit's outputs exactly.
- ⏳ **`provenance.circom` + `build.sh`** — the Groth16 circuit and trusted
setup. Compiling/proving needs `circom` (Rust) + `snarkjs`, which are not
bundled. `build.sh` produces `r1cs`, wasm witness gen, `*_final.zkey`,
`verification_key.json`, and `verifier.sol`.

## Build (needs circom + snarkjs)

```bash
npm install # circomlib (.circom sources) + circomlibjs (JS reference)
npm test # verify the scheme in pure JS — no toolchain needed
bash build.sh # compile + Groth16 setup -> proving/verification keys
```

## On-chain verification

The exported Groth16 verifier (`verifier.sol`) is the reference for the
on-chain check. In the protocol, the escrow program's `verifier` authority
(see `programs/poi-escrow`) is replaced by a Groth16 verifier that gates
`fulfill`/`slash` on a valid provenance proof + an unspent nullifier — closing
the loop with no human key in the path.
35 changes: 35 additions & 0 deletions circuits/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Compile provenance.circom and run the Groth16 trusted setup.
#
# Requires (not bundled — install separately):
# - circom >= 2.1 https://docs.circom.io/getting-started/installation/
# - snarkjs npm i -g snarkjs
# - circomlib provided as a devDependency (the .circom sources)
set -euo pipefail
cd "$(dirname "$0")"

CIRCUIT=provenance
PTAU=pot12_final.ptau

command -v circom >/dev/null || { echo "circom not installed: https://docs.circom.io/getting-started/installation/"; exit 1; }
command -v snarkjs >/dev/null || { echo "snarkjs not installed: npm i -g snarkjs"; exit 1; }
[ -d node_modules/circomlib ] || { echo "run 'pnpm install' (or npm install) here first for circomlib"; exit 1; }

# 1. Compile -> r1cs + wasm witness generator (circomlib on the include path).
circom "$CIRCUIT.circom" --r1cs --wasm --sym -l node_modules

# 2. Powers of Tau (universal phase-1). 2^12 constraints is ample for this circuit.
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="zeroquery" -e="$(head -c 32 /dev/urandom | xxd -p)"
snarkjs powersoftau prepare phase2 pot12_0001.ptau "$PTAU" -v

# 3. Groth16 setup -> proving + verification keys.
snarkjs groth16 setup "$CIRCUIT.r1cs" "$PTAU" "${CIRCUIT}_0000.zkey"
snarkjs zkey contribute "${CIRCUIT}_0000.zkey" "${CIRCUIT}_final.zkey" --name="zeroquery" -e="$(head -c 32 /dev/urandom | xxd -p)"
snarkjs zkey export verificationkey "${CIRCUIT}_final.zkey" verification_key.json

# 4. Export an on-chain verifier (Groth16). The Solidity verifier doubles as the
# reference for the Solana verifier program integration.
snarkjs zkey export solidityverifier "${CIRCUIT}_final.zkey" verifier.sol

echo "OK -> ${CIRCUIT}.r1cs, ${CIRCUIT}_js/, ${CIRCUIT}_final.zkey, verification_key.json, verifier.sol"
16 changes: 16 additions & 0 deletions circuits/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@zeroquery/circuits",
"version": "0.1.0",
"private": true,
"description": "ZeroQuery ZK provenance circuit (Groth16) + JS reference for the commitment/nullifier scheme.",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"test": "node --test reference.test.mjs",
"build": "bash build.sh"
},
"devDependencies": {
"circomlib": "^2.0.5",
"circomlibjs": "^0.1.7"
}
}
57 changes: 57 additions & 0 deletions circuits/provenance.circom
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
pragma circom 2.1.6;

include "circomlib/circuits/poseidon.circom";

/*
* Provenance — Zero-Knowledge attestation of intent fulfillment. (spec §3.5, §4.5)
*
* A responder proves it actually obtained some data (e.g. an API response) at a
* given time, and that the attestation is bound to its identity and to this
* specific intent — WITHOUT revealing the raw response or its private key.
*
* Private (witness): apiResponseHash, salt, privateKey
* Public (inputs): timestamp, intentHash
* Public (outputs): commitment, nullifier
*
* Constraints:
* commitment = Poseidon(apiResponseHash, timestamp, salt)
* binds the (hidden) data to a (public) timestamp; `salt` hides it so the
* commitment reveals nothing about the response — opening it later proves
* "I had this exact data at this time".
*
* nullifier = Poseidon(privateKey, intentHash)
* binds the proof to the responder's secret key AND this intent. The same
* key cannot produce two distinct nullifiers for one intent, so a verifier
* that records spent nullifiers gets replay protection; a false-attestation
* slash (spec §3.5) targets the nullifier without ever learning the key.
*
* Poseidon is used (not SHA-256) because it is SNARK-friendly — cheap inside the
* field arithmetic of a zk circuit.
*/
template Provenance() {
// --- private witness ---
signal input apiResponseHash;
signal input salt;
signal input privateKey;

// --- public inputs ---
signal input timestamp;
signal input intentHash;

// --- public outputs ---
signal output commitment;
signal output nullifier;

component c = Poseidon(3);
c.inputs[0] <== apiResponseHash;
c.inputs[1] <== timestamp;
c.inputs[2] <== salt;
commitment <== c.out;

component n = Poseidon(2);
n.inputs[0] <== privateKey;
n.inputs[1] <== intentHash;
nullifier <== n.out;
}

component main { public [ timestamp, intentHash ] } = Provenance();
50 changes: 50 additions & 0 deletions circuits/reference.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Pure-JS reference for the provenance scheme in provenance.circom.
*
* Computes the SAME Poseidon commitments/nullifiers the circuit constrains, so
* an off-chain attestation produced here matches the circuit's public outputs
* bit-for-bit. This lets the scheme be tested and used without the circom/snarkjs
* proving toolchain installed (the on-chain SNARK proof is generated via
* build.sh once that toolchain is available).
*/
import { buildPoseidon } from "circomlibjs";
import { createHash } from "node:crypto";

// BN254 scalar field prime (the field circom/snarkjs Groth16 operate in).
export const FIELD_PRIME =
21888242871839275222246405745257275088548364400416034343698204186575808495617n;

let _poseidon = null;
async function poseidon() {
if (!_poseidon) _poseidon = await buildPoseidon();
return _poseidon;
}

/** Reduce arbitrary bytes to a field element (e.g. hash a raw API response). */
export function toField(bytes) {
const h = createHash("sha256").update(bytes).digest("hex");
return BigInt("0x" + h) % FIELD_PRIME;
}

/** commitment = Poseidon(apiResponseHash, timestamp, salt) — returns a decimal string. */
export async function commitment(apiResponseHash, timestamp, salt) {
const p = await poseidon();
return p.F.toString(p([BigInt(apiResponseHash), BigInt(timestamp), BigInt(salt)]));
}

/** nullifier = Poseidon(privateKey, intentHash) — returns a decimal string. */
export async function nullifier(privateKey, intentHash) {
const p = await poseidon();
return p.F.toString(p([BigInt(privateKey), BigInt(intentHash)]));
}

/** Build the full public attestation an agent posts alongside its ZK proof. */
export async function attest({ apiResponse, timestamp, salt, privateKey, intentHash }) {
const apiResponseHash = toField(Buffer.from(apiResponse));
return {
timestamp: BigInt(timestamp).toString(),
intentHash: BigInt(intentHash).toString(),
commitment: await commitment(apiResponseHash, timestamp, salt),
nullifier: await nullifier(privateKey, intentHash),
};
}
Loading