# age-over-v1

1900 <= birthYear <= max, where max = (this year) - minAge - 1 (conservative). Proven without revealing the attribute, against a
commitment `Poseidon(value, salt)` the issuer recorded in SuperWalletTrustRegistry
(`commitment(credentialId, circuitId)`), and bound to the verifier's nonce.

| | |
|---|---|
| circuitId | `0xf88cc3077af352474814c6a6ced1355060cd8986a058b0c8bd310528157d8da7` = keccak256("age-over-v1") |
| vkHash | `0x7195c702a1711955b672f960a0bf2402420707678cf86d63595aec79e259d8f5` |
| ceremonyContributions | 1 (one Super Wallet contribution plus a public Xphere beacon; see TRANSCRIPT.md) |
| Committed attribute | `birthYear`: the year of birth (e.g. 1990) from the approved identity document |
| Public signals | `commitment`, `min`, `max`, `nonce` (snarkjs order; the commitment first, the nonce last) |
| Verifier call | `verifyProof(uint256[2],uint256[2][2],uint256[2],uint256[4])` |
| Request (`<sw-verify request>`) | `age>=19` -> predicate `{ "circuit": "age-over-v1", "params": { "minAge": 19 } }` |
| Registry | `0x852688D815410e3FEa015FD3a07028BF6b981d80` on Xphere (chain id 20250217). **Not registered as of 2026-09-27**: until `register-circuit` is sent, `circuit(0xf88cc307...)` returns the zero address and verifiers refuse this predicate |
| uri | https://trendy.storydot.kr/sdk/zk/age-over-v1/ |

## What a verifier checks

1. The circuit is registered and its vkHash is the one above; the proof passes `verifyProof` on
   the registered verifier contract (a view `eth_call`).
2. `publicSignals[0]` equals `commitment(credentialId, circuitId)` on the registry, the credential
   is anchored and not revoked, and its issuer is active.
3. The last public signal equals the nonce the verifier issued for this request (a proof made for
   another request fails the pairing check, and is refused before that).
4. The statement inputs are what was asked: `max` <= (verifier's current year) - minAge - 1 (conservative: everyone accepted is at least minAge on every day of the year); `min` >= 1900.

`@superwallet/verifier-sdk` `verify(response, { zkRequest })` and `<sw-verify request="age>=19">`
do all four.

## Files

| File | What it is |
|---|---|
| `age-over-v1.circom`, `templates/`, `lib/` | The circuit (circom 2.1.9; circomlib 2.0.5 for Poseidon, bitify and comparators) |
| `age-over-v1.r1cs` | The constraint system (321 constraints); needed to check the ceremony |
| `age-over-v1.wasm` | Witness calculator, for proving |
| `age-over-v1.zkey` | The final proving key; contains the whole contribution chain |
| `verification_key.json` | The Groth16 verification key |
| `Verifier.sol`, `solc-input.json` | The snarkjs Solidity verifier and the exact compiler input (solc 0.8.24, optimizer 200 runs, evmVersion paris) |
| `circuit.json` | Machine-readable manifest and ceremony record |
| `TRANSCRIPT.md` | The ceremony, step by step, with every hash |

## Reproduce

```sh
npm install circomlib@2.0.5 circom2@0.2.23
npx circom2 age-over-v1.circom --r1cs --wasm --O2 -l . -l node_modules
sha256sum age-over-v1.r1cs      # 2f32f5d7bb3c5f53396cce353362f0c5e2da5fda9455ada783bdaa6c8b6f3694

npx --yes solc@0.8.24 --standard-json < solc-input.json > solc-output.json
# keccak256(runtime bytecode of contracts/age-over-v1.Verifier.sol:Groth16Verifier) = 0xa03d7f7b30cbefe3ed6a91590f735d9648c7487d252769bbe6d4c410935532fc
# (../tier-threshold-v1/check-bytecode.cjs compares it with the deployed contract)
```

vkHash: RFC 8785 canonical JSON of `verification_key.json` (keys sorted, no whitespace), then
keccak256: `node scripts/governance.cjs vk-hash verification_key.json` in chain/trust-registry, or
`vkHash()` in @superwallet/zk-kit.
