01 · Product boundary

The useful primitive is a reusable claim, not another identity form

Applications often repeat the same workflow: collect identity data, send it to a provider, retain a result, and take on the security and compliance burden of handling it. StellarCred separates the issuer's job from the protocol's job. An issuer attests to a credential; the user proves a narrow statement about it; downstream protocols ask only whether the required claim has been verified.

That is why StellarCred should not be framed as a KYC app. KYC is one possible claim. The same architecture supports age, income, jurisdiction, and funds thresholds without requiring every integrating application to receive the underlying date of birth, country code, income, or balance.

02 · Architecture

Issue once, prove locally, verify on-chain, reuse everywhere

The issuer computes a Poseidon2 commitment from the private value and a random salt, then signs that commitment with secp256k1. The user retains the credential. In the browser, a Noir circuit proves knowledge of the committed value and verifies the issuer's signature without publishing the hidden input.

ProofRegistry submits the proof through CredentialVerifier, checks that the public issuer key belongs to an issuer authorized for that credential type, and caches the successful claim with an expiry. Every later protocol can read that state instead of verifying the expensive proof again.

1 · ISSUE

Trusted issuer commits to the credential value and signs the commitment.

2 · HOLD

The credential and its private opening remain with the user.

3 · PROVE

Noir generates an UltraHonk proof locally in the browser.

4 · VERIFY

Soroban performs genuine BN254 verification and validates the issuer key.

5 · CACHE

ProofRegistry records the claim and expiry for the holder address.

6 · REUSE

Any protocol checks the claim through a read-only contract call or SDK.

03 · Cryptographic binding

A valid proof must still come from a trusted issuer

Proving knowledge of some committed value is not sufficient. Without issuer binding, a user could create their own commitment and prove any claim they liked. Each circuit therefore verifies the issuer's secp256k1 signature over the commitment inside the proof.

The issuer public key is a public input. ProofRegistry compares it with the key registered for that issuer and credential type. The proof succeeds only when the hidden value opens the commitment, the signature is valid, and the signing key belongs to a trusted registered issuer.

C=Poseidon2(value,salt)VerifySignature(issuerKey,C)=true\begin{aligned} C &= \operatorname{Poseidon2}(\text{value},\text{salt}) \\ \operatorname{VerifySignature}(\text{issuerKey},C) &= \mathrm{true} \end{aligned}
The private value and salt stay hidden; the registered issuer key anchors the proof to an accepted trust root.

04 · Circuit model

Different claims disclose different minimum facts

The repository contains circuits for KYC status, age, income, jurisdiction, and funds. Binary claims answer a yes-or-no question. Numeric claims support thresholds, so a proof that establishes funds of at least 200,000 can satisfy a protocol requiring at least 50,000 without revealing the actual balance.

All five circuits share the same commitment scheme, but each has an independently installable verification key. That keeps protocol consumption uniform while allowing claim-specific constraints to evolve separately.

  • KYC → identity was verified; credential secret remains hidden
  • Age → age meets a minimum; date of birth remains hidden
  • Income → income meets a threshold; exact income remains hidden
  • Jurisdiction → country is permitted; country code remains hidden
  • Funds → balance meets a threshold; exact balance remains hidden

05 · Developer experience

Protocols integrate the result, not the credential workflow

An integrating application can use the SDK's hasClaim and getClaims methods or call ProofRegistry directly. If the user has not proved the required claim, buildVerifyUrl sends them through StellarCred and returns them to the original application after verification.

The important developer-experience decision is that protocols never handle credential data. Their policy becomes a small, reviewable claim check, while issuance and proving remain separate concerns.

A protocol consumes claims, not identity recordsts
const oldEnough = await StellarCred.hasClaim(wallet, "age", {
  minThreshold: 21,
});

const funded = await StellarCred.hasClaim(wallet, "funds", {
  minThreshold: 50_000,
});

06 · Engineering

Browser proving and on-chain verification are version-sensitive systems

Proof generation uses noir_js and bb.js in the browser, while Soroban verifies UltraHonk proofs through host-native BN254 support. Noir, Barretenberg, circuit artifacts, verification keys, and the contract verifier must agree exactly. A version mismatch can produce a proof that looks valid locally but cannot verify on-chain.

The test suite uses genuine proofs rather than mocked verifier success. Twenty-one contract tests cover the full protocol path across credential types. Bundle-size budgets also matter because browser proving assets are large enough to damage the user experience if loaded indiscriminately.

07 · Reflection

What I learned building the credential layer

The first lesson was that selective disclosure is as much about API boundaries as cryptography. A beautiful circuit still fails the product if every protocol must understand issuance internals or repeatedly invoke an expensive verifier.

The second was that trust does not disappear. StellarCred makes it explicit: issuers remain responsible for the truth of credentials, the registry decides which issuers are accepted, proofs preserve the hidden inputs, and protocols decide which claims satisfy their policy.

The result is useful because it minimizes repeated data exposure while preserving those roles. It is a reusable verification layer - not a claim that identity can become trustless merely by adding a proof.