# tier-threshold-v1

The circuit behind Super Wallet's credit-tier proofs: "the tier the issuer committed to is at least
N", without revealing the tier. Registered in SuperWalletTrustRegistry on Xphere mainnet
(chain id 20250217) as:

| Field | Value |
|---|---|
| circuitId | `keccak256("tier-threshold-v1")` = `0x4fc43435fda9467e9024d15eb45acad4360ce22118dd3092f3ac9dc3abf01109` |
| verifier | `0xc5887371D8A133a4b9F3827cE54558106c933d86` |
| vkHash | `0x4217de734b071f2bc8ca8a4403c5fabeda9f5614c638f76823e0a317a59685db` |
| ceremonyContributions | 1 |
| uri | `https://trendy.storydot.kr/sdk/zk/tier-threshold-v1/` (this directory) |
| registry | `0x852688D815410e3FEa015FD3a07028BF6b981d80` |

Read it yourself: `circuit(bytes32)` on the registry, e.g. `cast call 0x852688D815410e3FEa015FD3a07028BF6b981d80 "circuit(bytes32)(address,bytes32,uint16,string)" 0x4fc43435fda9467e9024d15eb45acad4360ce22118dd3092f3ac9dc3abf01109 --rpc-url https://en-hkg.x-phere.com`.

**One contribution means one party generated the proving key.** Whoever kept that party's
randomness could produce a proof for a false statement. The registry records the number as it is;
a new key from a multi-party ceremony would be registered under a new circuit id, never swapped in
under this one.

## Files

| File | What it is |
|---|---|
| `tier_threshold.circom` | The circuit (circom 2.1.9). Includes circomlib 2.0.5 `bitify`, `comparators`, `poseidon` via `../node_modules/circomlib/...` |
| `verification_key.json` | The Groth16 verification key (snarkjs format, BN254) |
| `TierThresholdVerifier.sol` | The Solidity verifier snarkjs generated from that key (contract `Groth16Verifier`) |
| `solc-input.json` | The exact solc standard-JSON input that reproduces the deployed verifier, byte for byte |
| `check-bytecode.cjs`, `keccak.cjs` | Compare a compilation with the deployed code (Node 18+, no packages) |

## What the circuit proves

Public signals, in the order snarkjs returns them: `commitment`, `threshold`. Private inputs:
`tier`, `salt`. It enforces `tier` and `threshold` in 0..4 (three bits), `Poseidon(tier, salt) =
commitment`, and `tier >= threshold`. Tiers: 0 POOR, 1 BELOW_AVERAGE, 2 FAIR, 3 GOOD, 4 EXCELLENT.

A valid proof says only that the statement holds for *these* public signals. A verifier must also
check that the commitment is the issuer's (`commitment(credentialId, circuitId)` on the registry)
and that the threshold is the one it asked for. `@superwallet/verifier-sdk`'s `verify()` and the
`<sw-verify>` widget do both.

## Recompute the vkHash

`vkHash = keccak256(JCS(verification_key.json))`: RFC 8785 canonical JSON (for this file: keys
sorted, no whitespace; it holds only strings, arrays and one small integer), then Ethereum
keccak256 of the UTF-8 bytes.

In a browser, on this page's index, press "Recompute". In Node 18+ with the SDK's keccak:

```bash
node -e '
const { keccak256Hex } = require("./keccak.cjs");
const sort = (v) => Array.isArray(v) ? v.map(sort) : v && typeof v === "object"
  ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sort(v[k])])) : v;
console.log(keccak256Hex(JSON.stringify(sort(require("./verification_key.json")))));'
```

With Python and pycryptodome:

```bash
python -c "import json; from Crypto.Hash import keccak; k=json.load(open('verification_key.json')); print('0x'+keccak.new(digest_bits=256, data=json.dumps(k, sort_keys=True, separators=(',',':')).encode()).hexdigest())"
```

Both print `0x4217de734b071f2bc8ca8a4403c5fabeda9f5614c638f76823e0a317a59685db`.

## Reproduce the verifier contract

```bash
npx --yes solc@0.8.24 --standard-json < solc-input.json > solc-output.json
node check-bytecode.cjs solc-output.json contracts/TierThresholdVerifier.sol:Groth16Verifier 0xc5887371D8A133a4b9F3827cE54558106c933d86
```

Settings inside `solc-input.json`: solc 0.8.24, optimizer on (200 runs), `viaIR: true`,
`evmVersion: paris`, source with LF line endings (the metadata hash at the end of the bytecode
covers the exact source bytes). Expected runtime keccak256:
`0x3fe2fb615185719ca3c909c74c255e0455ca378fa9857ff4e4149e86a23f4c86`.

## Rebuild the circuit

The repository builds it with `zk/scripts/setup.mjs`; the compile step is:

```bash
npm install circomlib@2.0.5 circom2@0.2.23 snarkjs@0.7.6
npx circom2 circuits/tier_threshold.circom --r1cs --wasm --sym --output build
```

The include paths (`../node_modules/circomlib/...`) expect the `.circom` file one directory below
the folder holding `node_modules/`, as in the repository's `zk/circuits/`. Compiling reproduces the
constraint system; the proving key (`.zkey`) is not published, because only the verification key is
needed to check proofs.
