Skip to content

Source-verification records: admitting a decentralized-consensus verifier to the Registry #516

Description

@ethanfrey

Who I am and what I want to build

I'm Ethan Frey — founder of the CosmWasm smart-contract engine for Cosmos. As part of
that work we built CosmWasm/optimizer, the
deterministic Wasm build image most CosmWasm contract verification has run on for years.
I'm now putting together the Soroban equivalent, and I want to flag the integration with
the Registry early — and concretely, against your actual contract code — so it slots
into what you've built rather than around it.

I want to build a decentralized-consensus source verifier for Soroban: a network of
independent, staked operators that takes a (source code, build image) pair — exactly
the inputs SEP-58 standardizes (bldimg, bldopt, a source identity) — rebuilds the
contract, and reaches consensus on whether the resulting Wasm hash matches the bytes
deployed on-chain. The output is a single, economically-backed proof that a specific
source tree compiles to a specific deployed Wasm. No central verifier; operator
disagreement is surfaced, not hidden.

Two payoffs, the second being the one I most want to co-design with you:

  1. Source verification. A deployed contract's opaque bytes get a trustworthy link
    back to reviewable source.
  2. Audits that actually map to deployed contracts. An audit is always performed
    against a specific source revision. Today nothing asserts that the audited source
    is the source that produced the deployed bytecode, so "audited by X" floats free of
    what's on-chain. Once a verifier proves source ↔ deployed-Wasm, an auditor's
    attestation against that source binds to the on-chain contract.

So this is not a competitor to the trusted-auditor model in your Item 4; it's the
foundation that makes auditor attestations bind to deployed contracts at all.

One terminology issue to settle up front

Registry already uses "verified" to mean manager-curated into the root registry
(website/docs/registry.md: the Verified (Root) Registry requires manager approval;
the Unverified Registry is open). That is a governance/curation signal.

What I'm describing is source verification: a cryptographic/economic proof that
this source produces these deployed bytes. It is orthogonal to manager-curation — a
contract in the unverified registry can be source-verified, and a manager-curated
("verified") contract may have no source proof. To avoid overloading the word, this
proposal calls it "source-verified" throughout and treats it as a per-Wasm-hash
record
, not a third registry and not a redefinition of your existing "verified". A
strong source-verification record is exactly the kind of objective signal that can
later inform manager curation, but it doesn't replace it.

Context

In discussion #1923,
@chadoh outlined a verify_source method storing Option<bool> per contract, gated to
a trusted-auditor subregistry, and noted it "could also be designed in a way to allow
multiple verifiers to store independent reviews." This is the concrete shape for the
multi-verifier case, mapped onto the primitives already in contracts/registry.

How this maps onto the contract as it exists today

I read the contract so this is additive, not hand-wavy. The relevant existing
primitives:

  • Manager-gated mutation + typed event + compact per-entry status is an established
    pattern: flag_contract(... flagged: bool) is gated by require_owner_or_manager,
    stores a compact flag on ContractEntry, and emits SecurityFlagContract for
    indexers. A source-verification record is the same shape of thing — manager-gated
    write, compact stored record, typed event the UI consumes.
  • manager is the gate. Managed registries require manager.require_auth() for
    publish/claim/batch/owner changes. In the rgstry deployment that manager is the Tansu
    Registry Security Council. "Gated by the Security Council review" therefore means
    exactly "callable only with the registry manager's auth" — no new gating concept.
  • Subregistries are child Registry instances, registered by a single crate-style
    name in the root and resolved via resolve_subregistry (the constructor already
    auto-deploys the unverified one; SubRegistry { name, contract_id } is emitted).
    The user-facing subregistry/name convention (unverified/my-contract) is parsed
    into (child registry, name) — it is not a slashed on-chain name. So auditor and
    verifier would be sibling subregistries alongside unverified, and
    verifier/avs-soroban-compiler is shorthand for "name avs-soroban-compiler in the
    verifier subregistry", consistent with how unverified/... already works.
  • Hashes are first-class keys. Storage.hash: PersistentMap<BytesN<32>, ()> already
    keys by Wasm hash via HashKey. Source verification is a property of a Wasm hash
    (which may be deployed as many contracts), so records should key by BytesN<32>,
    reusing that pattern.
  • SEP-58 surfacing is already in-scope for Q2. Issue stellar-scaffold/temp#13 explicitly adds
    stellar contract info meta (the SEP-58 fields) to Wasm/Contract detail pages. A
    source-verified badge belongs next to those fields on the same page.

Identity model

A human auditor is a person/company; a decentralized verifier has no single signer
the "signer" is an on-chain consensus aggregator contract. So:

  • Add sibling subregistries auditor and verifier next to the existing unverified
    one (same resolve_subregistry path, same root registration, each with its own
    manager). Names within them are crate-style single segments (≤64 chars, ascii
    alnum/-/_), e.g. verifier/avs-soroban-compiler, auditor/certora.
  • The registered principal for a verifier/* entry is a contract Address (the
    consensus aggregator), not a G-address. Admission of that entry goes through the same
    Tansu/manager review that gates root publishes.

Data model (illustrative; keyed by Wasm hash, matching the existing hash map)

// New: PersistentMap<BytesN<32>, Vec<VerificationRecord>, VerifyKey>
// One Wasm hash -> independent records from multiple verifiers. No aggregation.
#[contracttype]
pub struct VerificationRecord {
    pub verifier:      Address,       // registered consensus-aggregator (or auditor) contract/acct
    pub verifier_name: String,        // its name in the `verifier`/`auditor` subregistry
    pub result:        Option<bool>,  // None = in-flight; Some(true)=match; Some(false)=mismatch
    pub sep58:         Sep58Inputs,   // the (image, options) half of what was replayed
    pub source:        SourceRef,     // the source half: what bytes were rebuilt
    pub evidence:      Evidence,      // how the result was reached (provenance)
    pub trust_signals: Option<Bytes>, // opaque; schema = upstream SEP-58 companion SEP
    pub created_at:    u64,
}

#[contracttype]
pub struct Sep58Inputs { pub bldimg: String, pub bldopt: Vec<String> }

#[contracttype]
pub enum SourceRef {
    RepoRev { source_repo: String, source_rev: String },
    Tarball { tarball_url: Option<String>, tarball_sha256: BytesN<32> }, // sha256-alone = IPFS path
}

#[contracttype]
pub enum Evidence {
    Auditor   { signer: Address },                       // human/company auditor
    Consensus {                                          // decentralized verifier
        aggregator:         Address,
        operators_total:    u32,
        operators_agreeing: u32,                          // N-of-M; disagreement stays visible
        threshold:          u32,
        evidence_uri:       Option<String>,               // off-chain signed per-operator detail
    },
}

result: None in-flight (UI can show "verifying…"); Some(true) rebuilt bytes match
the deployed Wasm under the recorded SEP-58 inputs; Some(false) mismatch (a positive
signal the claimed source/inputs are wrong). operators_agreeing < operators_total is
preserved so the UI renders "8/9 operators agree", never a bare ✅.

Methods + event (illustrative, mirroring existing patterns)

// Write — manager-gated, exactly like flag_contract / managed publish.
// require_owner_or_manager-style: only the registry manager (Tansu council) or the
// registered verifier principal may write, and only its own (hash, verifier) slot.
fn submit_verification(env: Env, wasm_hash: BytesN<32>, record: VerificationRecord);

fn get_verifications(env: Env, wasm_hash: BytesN<32>) -> Vec<VerificationRecord>; // no aggregation
fn get_verification(env: Env, wasm_hash: BytesN<32>, verifier: Address) -> Option<VerificationRecord>;
// New typed event so the rgstry UI / StellarExpert index it, same as SecurityFlagContract.
#[contractevent(topics = ["source_verified"])]
pub struct SourceVerified { pub wasm_hash: BytesN<32>, pub verifier: String, pub result: Option<bool> }

The Registry never decides "is this verified?" — it stores independent records with
provenance and emits an event; consumers apply their own trust policy. That matches your
stated intent and SEP-58's verifier-policy-agnostic stance.

How auditor and verifier records coexist

auditor/* (human) verifier/* (decentralized)
Asserts a judgement about the source a mechanical fact: source → deployed bytes
Principal G-address (person/company) contract (consensus aggregator)
Evidence Auditor Consensus (N-of-M, disagreement visible)
Cadence manual, low volume automated, potentially every uploaded Wasm
Gate registry manager (Tansu council) registry manager (Tansu council) — same path

Both keyed on the same wasm_hash, so a consumer can require "source-verified and
audited" — which only works because both records sit together with provenance intact.
No human coordination point sits in the automated path.

Security / abuse considerations

  • Write authorization is the existing manager/owner auth path, not a new
    hardcoded list — admission still flows through the Tansu council review you run.
  • Record isolation: a verifier can only write its own (wasm_hash, verifier) slot.
  • Compactness: the contract already optimizes per-entry storage (the flagged
    length-of-vec trick); large per-operator detail stays off-chain behind evidence_uri,
    on-chain record stays small.
  • Staleness: created_at + optional re-verification; the Registry never adjudicates.
  • No oracle risk for the Registry: it stores claims with provenance; it does not
    itself verify. Trust is pushed to the consumer, exactly as with flagged.

What this is and isn't

  • Is: a concrete realization of your Item 4, expressed in the contract's own
    primitives (manager gate, BytesN<32> key, typed event, sibling subregistry), adding
    the source ↔ deployed-Wasm binding that lets audits map onto on-chain contracts.
  • Isn't: a competing registry, a new explorer, a redefinition of your "verified
    registry" curation term, or a change to who governs the Registry. The verifier writes
    here; rgstry and StellarExpert render it. MIT, co-designed in scaffold-stellar.

Open questions for the Registry team

  1. Storage home: verification records in the root, keyed by BytesN<32>
    (reusing the hash map pattern), or in a dedicated verifier subregistry
    instance? I lean root-keyed-by-hash since a hash maps to many contracts and stellar-scaffold/temp#13
    already surfaces per-Wasm meta — but it's your architecture call.
  2. Contract principal: the consensus aggregator is a contract Address, not an
    author G-address. Does the manager/auth path (and the named-address work in stellar-scaffold/temp#27 /
    SorobanDomains) accommodate a contract as the registered, authorized writer?
  3. Terminology: OK to surface this as a distinct "source-verified" signal on the
    stellar-scaffold/temp#13 Wasm-detail page, kept separate from the existing "verified registry" curation
    wording, so users aren't confused by two meanings of "verified"?
  4. Should an auditor/* record optionally reference the verifier/* record that
    proved source↔Wasm (making "audited ⇒ of the deployed bytes" explicit on-chain), or
    is composing the two left to consumers reading both? I lean consumer-composed.
  5. submit_verification overwrite-own-record (re-verification) vs append + versioning?
  6. Timing: stellar-scaffold/temp#13 (meta surfacing) is Q2; Item 4 ~Q3. Co-design this interface in Q2
    so it's ready when Item 4 lands, or target a later checkpoint?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions