Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

Credential Freshness Registry

A standalone GenLayer Intelligent Contract that reaches decentralized validator consensus on whether a described credential is listed on a pre-registered public issuer page, then lets that VERIFIED status decay over time (FRESH -> STALE -> EXPIRED) unless it is renewed by a fresh consensus round. Anyone can trigger a re-check; a credential the issuer no longer lists becomes REVOKED, permanently.

1. Live Deployment

Network GenLayer Studio network (studionet, Chain ID 61999, RPC https://studio.genlayer.com/api)
Contract 0x3Af7cF11A402c4c8c23974c18a057183ca8b6735
Deployer 0x6775E11e08FB78b5Ae7B0EE40E5ADa037775a171
Deploy tx 0xa06080e50de8715c922594cdf345ca8e1ba6a206db5001eef022db0b70574c80 - FINALIZED, execution SUCCESS

Demo transactions (all FINALIZED, leader execution SUCCESS)

Purpose Method Tx hash Validator agreement On-chain result
Register positive claim register_claim(...) 0x80706588d55e731f312bb34aea2f75d1fc609aa85491074978d180d234b6711b deterministic write claim "0" REGISTERED
Register negative claim register_claim(...) 0xf1335b2b7b44e45c1ad9fe9123f006af6832a3af16d25716552bb410ba6b6af4 deterministic write claim "1" REGISTERED
Positive: real RFC on issuer page verify_claim("0") 0xa3f3c7d93844ec97a6a60ced003f23ca1913f0fd1a5d0594cbc1bf2753da548d 3/5 AGREE, 0 DISAGREE status VERIFIED, last_band 2, freshness FRESH, is_verified true
Negative: fictitious RFC, same page verify_claim("1") 0x736796b3ef97eafb499aa40b377ce836a81a357cde5e17b8a3026c012af9ee65 3/5 AGREE, 0 DISAGREE status REJECTED, last_outcome NOT_LISTED, last_band 2

The exact inputs sent on-chain for both demos are committed verbatim in samples/ (same descriptors, same window parameters, same claim ids). Anyone can re-run them against the live contract or a fresh deployment.

Bradbury note. The contract was also deployed twice to the Bradbury testnet (Chain ID 4221) on 2026-07-26/27 - deploy txs 0xcf4f132b1d6ae590c7e9011c7ab477385ceb7e310ba9b214fa78558abf72b4c8 and 0xd1fbdc38f09c175c3367f4634e35c7120ba86258e984a9c042200ba0e38fb342, both FINALIZED with execution FINISHED_WITH_RETURN, plus two register_claim and one verify_claim that also finalized successfully. During that window Bradbury wiped GenVM contract state and its gen_call read path began returning "contract not found" network-wide (chain still producing blocks, balances intact), making contract state unverifiable there. The studionet deployment above was therefore chosen as the verifiable public evidence.

2. Problem and Why GenLayer

Credentials live on issuer web pages: professional registries, certification directories, membership rosters, publication indexes. Ordinary smart contracts cannot read those pages, and a single off-chain checker becomes the trusted party the whole design was trying to remove. Worse, credential verification is not a one-shot fact: certifications lapse, issuers revoke, pages change. A verification from six months ago proves nothing today.

GenLayer is used for exactly the irreducible part: multiple independent validators fetch the same pre-registered issuer page, run their own LLM evaluation of one natural-language question ("is this credential listed, and in what state?"), and must agree on minimal decision fields before anything is stored. Everything else - status transitions, freshness decay, revocation - is plain deterministic code.

When NOT to use GenLayer: if the issuer offers a signed API or on-chain attestation, use that directly - no consensus needed. If verification is needed only once, at one point in time, by one party that trusts its own scraper, a normal backend is sufficient. This primitive earns its consensus cost only when the answer must be trust-minimized, ongoing, and consumable by other contracts.

3. How It Works

register_claim                    verify_claim / reverify_claim (permissionless, cooldown-limited)
      |                                          |
      v                                          v
 claim stored with immutable          leader fetches issuer_url (7000-char cap)
 issuer_url + descriptor              -> LLM returns ONLY {listed, issuer_state, confidence, reason}
                                      -> each validator independently re-fetches and re-evaluates,
                                         then checks: exact listed match, exact issuer_state match,
                                         equal confidence band, cross-field invariants
                                                 |
                                                 v  (consensus reached)
                                      deterministic derivation in contract code:
                                      band -> outcome -> status transition -> timestamps
                                                 |
                                                 v
                                      oracle views: is_verified / get_freshness / get_claim
                                      (freshness derived at READ time from chain-pinned tx timestamps)

4. State Model, Lifecycle, Invariants

Claim status (stored): REGISTERED -> VERIFIED | REJECTED, REJECTED -> VERIFIED (issuer page may change), VERIFIED -> VERIFIED (renewal), VERIFIED -> REVOKED (terminal).

Freshness (never stored, derived at read time from last_verified_at): FRESH (age <= fresh_window) -> STALE (age <= expiry_window) -> EXPIRED. Windows are per-claim, bounded (fresh 1h..365d, expiry <= 730d), fixed at registration.

Invariants:

  1. is_verified == (status == VERIFIED and freshness == FRESH).
  2. Every status change derives from a consensus-validated payload; INCONCLUSIVE outcomes never change status or last_verified_at.
  3. REVOKED is terminal and only reachable from VERIFIED; a re-listed credential must be re-registered as a new claim, keeping revocation history immutable.
  4. Evidence is pre-registered: issuer_url and descriptor are immutable after registration, so challengers can never select evidence per dispute.
  5. No admin role exists anywhere in the contract.

5. Public API

Writes:

  • register_claim(subject_label, issuer_url, descriptor, fresh_window_secs, expiry_window_secs, recheck_cooldown_secs) - permissionless; duplicate (url, descriptor) pairs rejected.
  • verify_claim(claim_id) - first consensus verification (REGISTERED claims only).
  • reverify_claim(claim_id) - permissionless renewal / revocation challenge, rate-limited by the per-claim cooldown. REVOKED claims reject all writes.

Oracle views (for integrators):

  • is_verified(claim_id) -> bool - the one-line integration surface: consensus-verified AND still fresh.
  • get_freshness(claim_id) -> str - NONE | FRESH | STALE | EXPIRED for consumers with lenient policies.
  • get_claim(claim_id) -> str - full JSON decision record (status, outcome, band, timestamps, windows, counters).
  • find_claim(issuer_url, descriptor) -> str - claim id lookup ("" when absent).
  • get_claim_count() -> int, get_registry_version() -> str.

Claim ids are decimal-string sequence numbers ("0", "1", ...). SDK note: use genlayer-js or genlayer-py to pass claim ids; the genlayer CLI coerces numeric strings like "0" to integers, which the contract rejects with a clear UserError (fails closed, no state effect).

6. Consensus Design and Failure Behavior

The LLM returns four fields only: listed: bool, issuer_state: ACTIVE|REVOKED|NOT_FOUND|UNCLEAR, confidence: 0-100, reason (display-only). Calibration bands are published inside the prompt: 0-39 low, 40-79 medium, 80-100 high, and the prompt states that anything below 80 is treated as inconclusive by the protocol.

Each validator independently re-fetches the page and re-evaluates, then accepts the leader only if: payload is a well-formed gl.vm.Return, schema is valid (types, enum, range, bool-vs-int guard), cross-field invariants hold (ACTIVE/REVOKED => listed, NOT_FOUND => not listed), listed and issuer_state match exactly, and confidence bands are equal.

Deterministic derivation after consensus - every definitive outcome requires the high band, because a negative outcome can irreversibly revoke:

listed issuer_state band outcome effect
true ACTIVE 2 VERIFIED status VERIFIED, freshness reset
true REVOKED 2 ISSUER_REVOKED REJECTED, or REVOKED (terminal) if previously VERIFIED
false NOT_FOUND 2 NOT_LISTED REJECTED, or REVOKED (terminal) if previously VERIFIED
any any 0-1 INCONCLUSIVE no state change, streak incremented
any UNCLEAR any INCONCLUSIVE no state change

Failure behavior: unreachable/empty pages short-circuit to the canonical UNCLEAR payload without an LLM call; malformed or self-contradictory LLM output is canonicalized to UNCLEAR by the leader and independently rejected by validators via the invariant check; consensus failure aborts the transaction with no state write; the write path re-validates the payload deterministically as defense in depth.

7. Consensus Binding Matrix

Field Source Stored? Downstream effect Validator check Binding mode Differential test
listed LLM No (feeds derivation) outcome exact bool + invariant exact true vs false cannot both pass
issuer_state LLM No (feeds derivation) outcome exact enum + invariant exact ACTIVE vs REVOKED cannot both pass
confidence LLM No band only band equality band 75 vs 85 rejected; 85 vs 95 both pass with byte-identical stored state
last_band derived from consensused confidence Yes outcome gating - deterministic derive test_differential_same_band_converges_to_identical_state
last_outcome derived Yes status transition - pure function outcome table + 79/80 boundary tests
status derived Yes oracle views - transition table lifecycle tests
last_verified_at chain-pinned tx time Yes freshness decay - deterministic warp decay tests
freshness derived at read No is_verified - pure function FRESH/STALE/EXPIRED boundary tests
last_reason LLM Yes (display-only) none wording free display-only same-decision-different-wording test
invariant-violating payload LLM - - reject invariant listed=true+NOT_FOUND rejected though each field is valid

Raw confidence is never stored: validators only band-check it, so storing the raw integer would let two different numbers pass consensus while producing different state.

8. Security, Prompt Injection, Edge Cases

  • Issuer page content and the registrant-supplied descriptor are both treated as hostile. Evidence is JSON-encoded with <, >, & escaped to </>/&, wrapped under a SECURITY DIRECTIVE that instructions inside the data cannot override; a test asserts the escaping on the actual prompt string.
  • URL validation: HTTPS only, credential-in-URL rejected, localhost/private/link-local/reserved IP targets rejected, private DNS suffixes rejected, 512-char cap.
  • Page evidence capped at 7000 chars; descriptor 10-600 chars; reason truncated to 300 chars.
  • Rate limiting is cooldown-only (10min..30d per claim). There is deliberately no lifetime attempt cap: with permissionless re-verification, a cap would let any caller exhaust attempts and permanently block future renewals, forcing expiry (griefing). A test proves 500 hostile prior attempts cannot stop a later renewal.
  • All numeric decision values are integers; no floats anywhere in state or decisions.

9. Tests

pip install -r requirements.txt
genvm-lint check contracts/credential_freshness_registry.py
python -m pytest tests/ -q

Actual results for this working tree: lint passed (3 checks), 40 tests passed, 0 failed. Coverage: pure-function tables (band boundaries 39/40/79/80, outcome derivation, freshness edges), registration validation, full lifecycle including terminal REVOKED, warp-based decay and renewal, cooldown boundaries, consensus agreement/disagreement, per-field differential binding tests, invariant-violation rejection, error paths (unreachable page, malformed LLM output, UserError/VMError results), prompt-injection escaping, and griefing resistance.

On-chain evidence: the authoritative demo transactions are in the Section 1 table (studionet, real multi-validator consensus, real web fetch of the issuer page, real LLM evaluation). An earlier smoke-test instance on the same network reproduced the same positive/negative outcomes before the final deployment, and the observed INCONCLUSIVE behavior on unfetchable pages (PyPI blocks the GenVM fetcher) exercised the designed fail-closed path with zero state damage.

10. Why GenLayer (Engineering Notes)

Lessons from building and shipping this specific contract:

  • FINALIZED is not SUCCESS. A transaction can finalize while its GenVM execution errored (we produced exactly this with a mis-typed argument during testing). Always check the leader execution result before treating a receipt as evidence.
  • Store the band, never the number. Validators agree on confidence bands, not raw confidences - so the contract stores only the derived band. Storing the raw integer would silently break validator-state binding (85 vs 95 both pass, different state).
  • Terminal transitions demand the highest evidence bar. Revocation is irreversible, so NOT_LISTED / ISSUER_REVOKED require the same high band as VERIFIED; confidence 79 changes nothing, 80 is definitive - and there are boundary tests for both sides.
  • Never put a lifetime cap on a permissionless path. An attempt cap looked like prudent rate limiting and was actually a griefing vector (exhaust the cap, force expiry). Cooldown limits rate; caps create permanent denial.
  • Evidence is hostile and must be capped. Web pages can inject instructions, so evidence is escaped, boxed under a security directive, and truncated - and unreachable pages resolve to a canonical inconclusive payload without ever calling the LLM.
  • Time belongs to the VM, not the caller. Freshness decay derives from GenVM-pinned transaction timestamps and is computed at read time; the contract never trusts caller-supplied clocks and never stores a freshness label that could go stale.

11. Reusable Integrations

The oracle surface is designed for other contracts (see examples/credential_gate_consumer.py for a working pattern using @gl.contract_interface):

  1. DAO / role gating - grant proposal or voting rights only while is_verified(claim_id) is true; members renew via permissionless reverify_claim before the freshness window lapses.
  2. Escrow / hiring milestones - release payment only when the contractor's professional credential is VERIFIED and FRESH at release time, not merely at contract signing.
  3. Marketplace trust badges - map get_freshness to UI states (FRESH badge, STALE warning, EXPIRED removal) without running any off-chain verification infrastructure.

12. Limitations

  • Truth source is the registered issuer page as rendered text: if an issuer page is JavaScript-only, paywalled, bot-blocked, or lists the credential beyond the 7000-char evidence cap, claims resolve INCONCLUSIVE (fail-closed, never a false verdict).
  • The registry does not prove the identity of the claim registrant - it proves what the issuer page says about the described credential. Identity binding belongs to the consumer contract.
  • GenLayer test networks may reset state periodically (Bradbury did exactly this during this project's deployment window - see the note in Section 1); the samples/ inputs allow anyone to reproduce the demos against a fresh deployment on any GenLayer network.
  • One evidence URL per claim by design (pre-registered, immutable); multi-source aggregation is a different primitive.

13. Repository Structure

contracts/credential_freshness_registry.py   # the contract (single Contract class)
tests/test_credential_freshness_registry.py  # 40 direct tests (mocked LLM/web + warp time)
samples/positive_rfc2606.json                # exact on-chain input of the positive demo
samples/negative_rfc99999.json               # exact on-chain input of the negative demo
examples/credential_gate_consumer.py         # integration example consuming the oracle views
docs/consensus.md                            # consensus design specification
README.md                                    # this document
gltest.config.yaml                           # test runner configuration
requirements.txt                             # pinned toolchain
LICENSE                                      # MIT
.gitignore                                   # cache/artifact exclusions

14. License

MIT - see LICENSE.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages