primer · noir

Five Noir primitives, demystified.

Noir is an open-source language for zero-knowledge circuits: you write a statement, the prover generates a proof that it holds, and a verifier checks it without ever seeing the private inputs. Every idea in this catalog leans on one of five primitives, all provable in the browser with Noir and Barretenberg.

assert a bound, reveal a verdict

Private Predicate

Primitive: a Noir circuit takes the sensitive value as a private input and the bound as a `pub` input, then states the claim as an assertion — `fn main(dob_year: u32, threshold: pub u32) { assert(dob_year <= threshold) }`. A proof exists only when the assertion holds, so the verifier learns the verdict and nothing else: no age, no salary, no score, no location

UI: a single gate that answers one yes/no question — old enough, rich enough, close enough, qualified enough — while the underlying number never leaves the holder's device

hash a secret, publish the handle

Commitment Binding

Primitive: the circuit hashes the private witness and returns the digest as a public output — `std::hash::pedersen_hash([secret_lo, secret_hi])` over the two 128-bit limbs of a SHA-256 digest, because a 256-bit hash does not fit in one BN254 field. Pedersen is deterministic, so the same secret always yields the same commitment: two proofs can be linked to one hidden document, and a verifier can recognise it without ever seeing it

UI: a receipt or fingerprint that proves 'this proof is about that exact file, licence or take' — reusable across sessions, unlinkable to its contents

in the list, without saying which row

Set Membership

Primitive: publish a Merkle root of the allowed set, then prove a private leaf and its private sibling path hash up to that public root. The circuit recomputes the path and asserts equality with the root, so membership is proved while the identity of the member — and the size and order of the set — stay private

UI: a door, allowlist or roster check where the visitor proves they are on the list without revealing which entry they are, and the venue never receives a copy of the list membership

check the issuer, hide the document

In-Circuit Signature

Primitive: verify an ECDSA (secp256k1/secp256r1) or EdDSA signature inside the circuit — `std::ecdsa_secp256k1::verify_signature(pub_key_x, pub_key_y, signature, message_hash)` — with the signed payload private and the issuer's public key public. The proof says 'a named authority signed something with these properties' without publishing the signed document itself

UI: a verification step that trusts an issuer's attestation — a licence, a diploma, a payslip, a lab result — while the attestation and its contents stay with the holder

a proof about proofs

Recursive Aggregation

Primitive: verify one proof inside another with `std::verify_proof`, folding several independent claims into a single succinct proof. The inner proofs' public inputs become private witnesses of the outer circuit, so a chain of attestations, a batch of transactions or a season of contributions collapses to one artefact a verifier checks once

UI: a portfolio, ledger or history that is checked in one step — many separate private facts summarised as a single proof, with no per-item disclosure

Three ways to run the agent.

Every mega-prompt in the catalog is written for one of three modes. Pick the mode on any idea page and the prompt rewrites itself.

  • no setup · no secrets

    Browser prover

    Compile the Noir circuit at runtime with noir_wasm, generate the witness with noir_js, and prove and verify with Barretenberg UltraHonk — all inside the visitor's browser. Private inputs never leave the page.

    Secrets: none

  • ship the ACIR artifact

    Precompiled circuit

    The circuit is compiled ahead of time with nargo and the ACIR JSON is committed to the repo. The browser loads only the prover, so the first proof starts seconds sooner.

    Secrets: none

  • nargo · bb · Solidity verifier

    Native + on-chain

    Develop and prove with the native nargo/bb CLI, generate a Solidity verifier from the verification key, and have the app check proofs against the deployed contract.

    Secrets: VITE_RPC_URL, VITE_VERIFIER_ADDRESS

Gotchas worth memorising.

The Noir docs leave a few edges ambiguous. These bite hardest in a one-shot Lovable build.

  • Publish before you issue

    Only a PUBLISHED did:prism carrying an `assertionMethod` key can sign a credential offer. Create → publish → wait for `PUBLISHED`, or issuance fails with a cryptic 422.

  • Fly serves at the root

    A direct Fly deploy has no APISIX gateway, so strip any trailing `/cloud-agent` from the base URL. The local docker compose stack keeps it.

  • Everything is asynchronous

    POST creates a record; you then poll `protocolState` — `OfferSent` → `CredentialSent`, `RequestSent` → `PresentationVerified`. Never assume the POST finished the job.

  • Connectionless needs a goalCode

    Issuing without an established connection means omitting `connectionId` and supplying a `goalCode`. Send neither and you get "Missing connectionId".

  • DIDComm needs a reachable host

    `DIDCOMM_SERVICE_URL` must point at a real, publicly reachable host with port 8090 published. A placeholder makes every invitation undeliverable.

  • Give the agent room

    First boot migrates four databases. Allow ~5 minutes and at least 4 GB of memory before deciding the agent is broken — under that it gets OOM-killed mid-migration.

For your own LLM

Everything above, in one plain-text file.

Primitives, agent modes, REST shapes, failure modes and all 1,000 catalog entries — formatted for pasting straight into ChatGPT, Claude or your editor's assistant.