From 050fe755fad62f83052a295c0dc80b24f01a5e13 Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 12 Jun 2026 16:45:20 -0300 Subject: [PATCH 1/4] Grant proposal (docs/PROPOSAL.md) for the SCF Contract Source Verification Service RFP Full 13-section proposal per STE-52: MVP evidence, SEP-58 field-by-field pipeline mapping with Mermaid diagram, all three source modes + IPFS, image trust tiers with eviction-downgrade semantics, multi-verifier ed25519-signed result model, /v1 API spec with verifier-API SEP conformance commitment, submission flows incl. retroactive off-chain metadata path, threat model, ops/sustainability, repo-grounded prior art (Sourcify, solana-verify, stellar-expert/soroban-build-workflow, stellar-cli #2585/#2586), RFP-deliverable-mapped milestones, integrations plan, compliance, and a requirements traceability appendix. Closes STE-52 --- docs/PROPOSAL.md | 779 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 779 insertions(+) create mode 100644 docs/PROPOSAL.md diff --git a/docs/PROPOSAL.md b/docs/PROPOSAL.md new file mode 100644 index 0000000..adc91d7 --- /dev/null +++ b/docs/PROPOSAL.md @@ -0,0 +1,779 @@ +# Soroscan Verify — Contract Source Verification Service + +**Grant proposal in response to the SDF RFP "Contract Source Verification Service" (Q2 2026)** + +- **Applicant:** Bleu (bleu.studio) +- **Repository:** `scf-contract-source-verification` (Apache-2.0, public) +- **Working MVP:** chain reader, verify-by-contract-ID CLI, pinned Docker build toolchain, live testnet fixture with a deterministic on-chain hash match +- **Date:** June 2026 + +--- + +## 1. Summary & current state + +Soroban contracts are deployed as opaque Wasm blobs. The ledger stores uploaded +bytecode in a `ContractCodeEntry` keyed by the SHA-256 of the executable, and a +deployed instance references that code by hash. Source verification therefore +reduces to a single falsifiable question: *can a candidate source tree be +rebuilt into a Wasm whose SHA-256 equals the hash the ledger reports for this +contract?* This proposal describes a hosted, free, public verification service +that answers that question at ecosystem scale: it consumes the SEP-58 metadata +vocabulary (`bldimg`, `bldopt`, `source_repo`, `source_rev`, `tarball_url`, +`tarball_sha256`), rebuilds source inside SDF-allowlisted trusted build images, +byte-compares the result against deployed Wasm, and serves signed verification +results to explorers, wallets, and the Stellar CLI through a stable, versioned +API — so the ecosystem gets one shared result layer instead of every consumer +re-running rebuilds or trusting a single hardcoded verifier. + +We are not proposing to start from zero. The public MVP repository already +proves the core verification primitive end to end: + +- **A deterministic rebuild matches the chain.** A sample Soroban contract + (`contracts/hello-soroban`, soroban-sdk 25.3.1, target `wasm32v1-none`) is + deployed on testnet as contract + `CDVSGPL3HFBGJ6ZEYQUAVE3OH3XE2ZE5ZT2GWPA3LKOYVD4UBPQJ2VHB`. Rebuilding its + source with the pinned toolchain (`stellar contract build --locked`, rustc + 1.91.1, stellar-cli 26.1.0) produces a 1,060-byte Wasm whose SHA-256 — + `6fe7bd58e5a33dc27daefc74acfae6eb70f101fdbde860475cf18fde87288e4b` — equals + (a) the hash the CLI prints at upload, (b) the hash the ledger stores in the + `ContractCodeEntry` (confirmed via `stellar contract fetch`), and (c) the + hash our chain reader computes from the Wasm it pulls over RPC. Two clean + rebuilds on the same toolchain produce the identical hash: determinism is + demonstrated, not assumed. +- **A chain reader over Stellar RPC.** `reader/src/chain-reader.ts` fetches + on-chain Wasm and computes its SHA-256 by contract ID + (`getContractWasmByContractId`) or directly by Wasm hash + (`getContractWasmByHash`) using `@stellar/stellar-sdk`'s `rpc.Server` — the + by-hash path matters because one verified hash covers every contract + instance that references it. +- **A verify-by-ID CLI with a graded verdict model.** `soroscan-verify verify + --id --wasm ` compares the rebuilt artifact against the + chain and returns one of four verdicts: `FULL_MATCH` (byte-identical), + `METADATA_ONLY_MATCH` (differs only in the SEP-46 `contractmetav0` custom + section — detected structurally by parsing the Wasm section table and + re-comparing with that section stripped), `NO_MATCH`, or `ERROR`. The CLI + exits 0 only on `FULL_MATCH`, making it scriptable in CI. +- **A pinned, network-isolated build environment.** `docker/Dockerfile` pins + rustc/cargo, the stellar-cli version, and the `wasm32v1-none` target, and the + rebuild runs with `--network=none` — compile-time network isolation is + already demonstrated, not planned. `docker/toolchain-manifest.json` records + the resolved toolchain so a third party can re-derive the same hash from the + same image. + +The grant funds the path from this proven primitive to the service the RFP +asks for: a multi-verifier result layer with signed verdicts, all three SEP-58 +source modes, an SDF-anchored image allowlist with explicit trust tiers, a +free public query API, retroactive verification for already-deployed +contracts, explorer and wallet integrations, a third-party security audit +through the SDF audit bank, and a production operations posture. + +## 2. Architecture & SEP-58 alignment + +SEP-58 ("Contract Build Reproducibility for Verification", draft by Leigh +McCulloch) defines a storage-independent vocabulary that lets anyone re-run a +contract's build and compare the output to the deployed bytes. The service is +architected as a direct consumer of that vocabulary: every field in SEP-58 +drives a specific, auditable pipeline step, and nothing in the pipeline +depends on metadata outside the SEP. Canonically the fields live in the Wasm's +`contractmetav0` custom section (per SEP-46); the service also accepts the +same fields through off-chain submission for contracts deployed before the +tooling existed (§7). + +| SEP-58 field | Semantics (per the SEP) | Pipeline step it drives | +|---|---|---| +| `bldimg` | Fully-qualified container image, **pinned by digest** | Image resolution: the digest is checked against the allowlist (§4), assigned a trust tier, pulled by digest — never by tag — and used as the rebuild environment. Tag-only references are rejected at intake. | +| `bldopt` | One shell-style flag per entry, passed verbatim as a single argument | Build invocation: each recorded flag is appended, verbatim and in order, to the `stellar contract build` command inside the container. No flag interpolation or shell evaluation occurs on the host. | +| `source_repo` | HTTPS URL of the source repository | Source acquisition, mode 1: clone the repository (no system-git dependency in the worker). | +| `source_rev` | Full 40-character commit SHA-1 | Source acquisition, mode 1: check out exactly this commit; branch or tag names are not accepted as substitutes. | +| `tarball_url` | URL where the source tarball can be downloaded | Source acquisition, mode 2: download the tarball over HTTPS or IPFS (§3). | +| `tarball_sha256` | SHA-256 of the tarball bytes | Integrity gate and content address: the downloaded bytes are hashed and must equal this value before extraction (mode 2); alone, it is the lookup key for content-addressed private source (mode 3). | + +The end-to-end flow: + +```mermaid +flowchart TD + subgraph Claim["Claim channel (SEP-58)"] + Meta[contractmetav0 fields in deployed Wasm - SEP-46] + Retro[Off-chain metadata submission - retroactive path] + end + Dev[Developer: CLI or web] -->|POST /v1/verifications| API[Public API - versioned /v1] + Explorer[Explorers / wallets / CLI] -->|GET /v1/contract/id - GET /v1/wasm/hash| API + API --> Reader[Chain reader - Stellar RPC, mainnet + testnet] + Reader -->|getContractWasmByContractId / getContractWasmByHash| Ledger[(ContractCodeEntry - SHA-256-keyed Wasm)] + Reader --> Meta + Retro --> Queue + API -->|enqueue job| Queue[Build queue] + Queue --> Acquire[Source acquisition - repo@rev, tarball, content-addressed] + Acquire --> Store[(Tamper-evident content-addressed artifact store + IPFS pin)] + Acquire --> Allow{bldimg digest vs allowlist} + Allow -->|trust tier recorded| Worker[Rebuild worker - pulled by digest, network-isolated, ephemeral] + Worker -->|stellar contract build + recorded bldopt| Rebuilt[Rebuilt Wasm + SHA-256] + Rebuilt --> Compare{Byte compare vs on-chain Wasm} + Compare -->|FULL_MATCH / METADATA_ONLY_MATCH / NO_MATCH / ERROR| Sign[ed25519-signed verification result] + Sign --> DB[(Verification registry - append-only)] + Peers[Other self-hosted verifiers] -->|signed results| DB + DB --> API +``` + +Every box above the registry already has a working ancestor in the MVP repo: +the chain reader, the comparison and verdict logic, and the pinned +network-isolated rebuild container are running code; the queue, registry, +signing layer, and public API are the grant-funded build-out. The service +operates against **both mainnet and testnet** from launch — the MVP's network +resolver is testnet-only by deliberate guard, and lifting that guard plus +mainnet RPC configuration is part of milestone M2 (§11). + +A deliberate architectural property: verification is keyed by **Wasm hash**, +not contract ID. The contract-ID endpoint resolves the instance's current code +reference and joins it to verifications of that hash, so one successful +verification covers every instance sharing the bytecode, and contract upgrades +(re-pointing to a new hash) naturally surface as "this instance's current code +is unverified" rather than stale green badges. + +## 3. Source modes + +The service supports all three SEP-58 source modes as first-class citizens — +none is an afterthought, because each serves a real publishing posture in the +ecosystem. + +**Mode 1 — public repository (`source_repo` + `source_rev`).** The worker +clones the repository and checks out the exact recorded commit. Because a git +commit SHA-1 is not a content commitment over the *built tree* with the +strength we want for long-term records, the worker then produces a canonical +source tarball of the checked-out tree and records its SHA-256 — so every +verification, regardless of mode, ends up anchored to a content-addressed +source artifact. If the repository later disappears or rewrites history, the +verification record still points at an artifact we (and IPFS) hold. + +**Mode 2 — hosted tarball (`tarball_url` + `tarball_sha256`).** The service +downloads the tarball and verifies its SHA-256 against the recorded value +before extraction; a mismatch is a hard failure recorded as `ERROR` with a +machine-readable cause, never a silent fallback. **IPFS is a first-tier +retrieval channel alongside HTTPS**: `tarball_url` may be an `ipfs://` URI (or +a gateway URL), and for HTTPS-hosted tarballs the service pins a copy to IPFS +after integrity-checking it, then records the CID in the verification result. +This gives every verified contract a retrieval path that does not depend on +the original host staying alive. + +**Mode 3 — content-addressed private source (`tarball_sha256` alone).** This +mode exists for teams whose source is not public but who still want +independent verification — typically auditor-mediated: the team provides the +tarball directly to one or more verifiers (or to an auditor operating a +verifier instance), each of which checks the hash, rebuilds, and publishes a +signed verdict *without republishing the source*. The public record then says: +"source with digest X, rebuilt in image Y, produces the deployed bytes — +attested by verifiers A and B." Consumers learn exactly as much as the +publisher chose to reveal, and the content address means any future disclosure +of the tarball is mechanically checkable against the original claim. The +multi-verifier architecture (§5) is what makes this mode meaningful: several +independent parties can corroborate a rebuild of source the public cannot see. + +**Tamper-evident artifact storage.** All source artifacts — canonical tarballs +from mode 1, downloaded tarballs from mode 2, auditor-submitted tarballs from +mode 3 where the submitter permits retention — are stored content-addressed by +their SHA-256 in an append-only store, mirrored to IPFS where licensing +permits. An artifact can be added but never mutated in place; the digest in +the signed verification result is the integrity check, so tampering with +stored bytes is detectable by any reader recomputing the hash. This satisfies +the RFP's tamper-evidence requirement structurally rather than procedurally. + +## 4. Trust model + +A verification result is only as meaningful as the environment that produced +the rebuild. The service makes that trust explicit instead of implicit, along +two axes: *which build image was used* and *what kind of evidence backs the +verdict*. + +### Image trust tiers and the allowlist + +Every verification records the `bldimg` digest it ran and the **trust tier** +of that digest at evaluation time: + +- **`sdf-trusted`** — the digest appears on the SDF-published allowlist. The + default allowlist is the set of digests SDF publishes for + `stellar/stellar-cli` images from the `stellar-cli-docker` repository, which + is explicitly "compatible as a SEP-58 image for reproducible Stellar + contract builds" and whose own documentation mandates pinning "to a per-arch + single-architecture digest (`@sha256:…`) — it is the only stable reference." + The service will consume SDF's allowlist endpoint directly once it is + available, so allowlist updates require no service release; until then the + allowlist is a signed, versioned configuration file seeded from the + published `stellar-cli-docker` digests. +- **`publicly-auditable`** — a third-party image admitted through a documented + governance process: the image's Dockerfile and build context must be public; + the image must itself be reproducibly buildable from that context; the + digest must be published by the maintainer; and admission requires a public + review window (proposed as a PR against the allowlist repository, open for + comment, with the rationale recorded). This tier exists so the ecosystem is + not bottlenecked on SDF publishing an image for every toolchain combination, + without diluting what `sdf-trusted` means. +- **`arbitrary`** — any other digest-pinned image. The service still performs + the rebuild (the result is real evidence — the bytes matched or they + didn't), but the tier tells consumers the environment itself is unvetted. +- **`unknown`** — the recorded digest can no longer be resolved or its + provenance can no longer be established. + +**Allowlist eviction downgrades; it never deletes.** If a digest is removed +from the allowlist (for example, a vulnerability is found in an image), past +verifications that used it are *not* erased — erasing them would destroy the +historical record and break every consumer that cached a result. Instead, the +trust tier recorded on those verifications is downgraded, the tier-change +event is appended to the verification's history with a timestamp and reason, +and API responses immediately reflect the new tier. Consumers that filter on +`sdf-trusted` see the downgrade at once; consumers that want the full history +can read it. Trust signals age; evidence does not. + +### SEP-55 and SEP-58: two answers to two different questions + +The service treats SEP-55 attestations and SEP-58 rebuild verification as +**complementary, distinct trust levels — neither subsumes the other**, and the +API exposes them as separate fields rather than collapsing them into one +badge. SEP-58's own text says it is "complementary to SEP-55, which uses +signed CI attestations instead of independent rebuild… The two address the +same trust question with different trade-offs," and we adopt exactly that +framing: + +- **SEP-55** ("Contract Build Verification") answers: *did a trusted CI + pipeline build this Wasm from this repository?* The evidence is a GitHub + artifact attestation signed via GitHub's OIDC infrastructure — provenance + from a named builder, available the moment the build runs, with no rebuild + cost. +- **SEP-58 rebuild verification** answers: *does this source, built in this + environment, produce exactly these bytes?* The evidence is an independent + re-execution by a party with no stake in the original build. + +A contract can — and ideally does — carry both: CI provenance for freshness +and a named builder, independent rebuilds for source↔binary correspondence +that doesn't depend on any single CI provider. Every API response carries +`sep55_attestation` as its own field (present/absent plus attestation +details when present), alongside the rebuild verdict, so explorers can render +both signals and consumers can apply their own policy about which they +require. During the grant we will detect and surface existing SEP-55 +attestations for every contract we index, so the service strengthens the +SEP-55 ecosystem rather than competing with it. + +## 5. Multi-verifier architecture & decentralization + +The RFP is explicit that the ecosystem needs a shared result layer with no +single hardcoded verifier, and the architecture delivers that as a structural +property rather than a promise: + +**Every verifier instance is self-hostable.** The verifier is our open-source +codebase (Apache-2.0); anyone — an explorer, an auditor, a foundation, a +skeptical developer — can run one from the public repository and Docker +images. There is no closed component, no privileged build, and no +functionality reserved for our deployment. + +**Every verifier holds an ed25519 identity key and signs each result.** A +verification result is the canonical encoding of: the on-chain Wasm SHA-256, +the source artifact digest (the content address from §3), the verdict, the +`bldimg` image digest, and the timestamp — signed with the instance's ed25519 +key. Signatures make results portable: a result fetched from our API, relayed +through an explorer's cache, or mirrored by a third party carries its own +proof of which verifier produced it and that it was not altered in transit. + +**The public API serves results from all known verifiers.** Self-hosted +instances can register their public key and result endpoint +(`GET /v1/verifiers` lists them; registration is open and permissionless, +subject only to anti-abuse rate limits), and the aggregation layer ingests and +serves their signed results next to our own. A query for a contract returns +*the set of per-verifier results*, not a single merged opinion. + +**Consumers configure a trusted-verifier set.** An explorer might trust SDF's +instance and ours; a wallet might require two independent `FULL_MATCH` +results; an auditor might trust only their own instance. The client SDK ships +with this policy surface built in. + +**Disagreement is rendered per-verifier, never averaged away.** If verifier X +reproduces the bytes and verifier Y does not, the API reports both, and the +reference UI renders exactly that: + +``` +[√] Verified by X FULL_MATCH image: sdf-trusted +[!] Mismatching verification by Y NO_MATCH image: sdf-trusted +``` + +Disagreement between verifiers using the same image digest and source artifact +is a loud, important signal — it means either non-determinism in the toolchain +or a misbehaving verifier — and hiding it behind a majority vote would discard +precisely the information that matters most. + +**Our hosted deployment is the first instance, not a privileged one.** It +bootstraps the network and gives explorers something to integrate against on +day one, but nothing in the protocol distinguishes it: it signs with its key +like any other instance, appears in `GET /v1/verifiers` like any other +instance, and can be dropped from any consumer's trusted set. The +decentralization goal of the RFP is met by making the hosted service +replaceable from the start. + +## 6. Public API specification + +The API is free, public, unauthenticated for reads, and versioned under +`/v1`. The four core endpoints: + +- **`GET /v1/contract/{id}`** — resolve a contract ID to its current Wasm + hash and return all known verifications of that hash (plus, for upgraded + contracts, pointers to verifications of previously-referenced hashes). +- **`GET /v1/wasm/{hash}`** — all known verifications for a Wasm hash; the + canonical lookup, since verification is keyed by bytecode. +- **`POST /v1/verifications`** — submit a verification request: a target + (contract ID or Wasm hash), the SEP-58 fields (read from `contractmetav0` + when present, supplied in the request body for the retroactive path), and + the source mode. Returns `202` with a job resource that resolves to the + signed result. +- **`GET /v1/verifiers`** — the registry of known verifier instances: ed25519 + public key, operator metadata, endpoint, and status. + +Response schema sketch for a single verification record: + +```jsonc +{ + "wasm_hash": "6fe7bd58e5a33dc27daefc74acfae6eb70f101fdbde860475cf18fde87288e4b", + "network": "testnet", + "verdict": "FULL_MATCH", // FULL_MATCH | METADATA_ONLY_MATCH | NO_MATCH | ERROR + "sep58": { // the recorded claim, verbatim + "bldimg": "docker.io/stellar/stellar-cli@sha256:…", + "bldopt": ["--locked"], + "source_repo": "https://github.com/org/contract", + "source_rev": "0e01e07…(40 hex chars)", + "tarball_url": null, + "tarball_sha256": null + }, + "source_mode": "repo", // repo | tarball | content-addressed + "source_artifact": { // what was actually rebuilt (§3) + "sha256": "…", + "ipfs_cid": "bafy…", // null when retention not permitted (mode 3) + "retained": true + }, + "image_trust_tier": "sdf-trusted", // sdf-trusted | publicly-auditable | arbitrary | unknown + "image_trust_history": [ // populated on downgrade (§4) + { "tier": "sdf-trusted", "at": "2026-07-01T12:00:00Z", "reason": "allowlisted" } + ], + "sep55_attestation": { // distinct field; never merged into verdict + "present": true, + "builder_id": "https://github.com/org/contract/.github/workflows/release.yml@…" + }, + "verifier": { + "id": "soroscan-main", + "public_key": "ed25519:…", + "signature": "…" // over (wasm_hash, source_artifact.sha256, verdict, bldimg digest, timestamp) + }, + "created_at": "2026-07-01T12:03:41Z", + "verified_at": "2026-07-01T12:05:12Z" +} +``` + +**Versioning policy.** `/v1` is stable from the moment it is documented: +changes within v1 are strictly additive (new fields, new optional +parameters); anything breaking ships as `/v2` with both versions served +through a published deprecation window of at least six months. **We commit +explicitly to conforming to the forthcoming verifier-API SEP once it is +authored**: when that SEP lands, we will implement it as the next API version, +serve it alongside `/v1` during transition, contribute implementation feedback +to the SEP process from our production experience, and ship the client SDK +against the SEP-conformant surface. (The verifier-API SEP does not yet exist +as a numbered document; we treat conformance as a tracked deliverable, not a +dependency that blocks launch.) + +Badge and embed endpoints for explorers (`GET /v1/badge/{contractId}.svg` and +a JSON embed payload) ride on the same data and are described in §12. + +## 7. Submission flows + +**CLI submission.** The natural developer flow builds on the in-progress +stellar-cli work rather than competing with it. PR +[stellar-cli#2585](https://github.com/stellar/stellar-cli/pull/2585) adds +`stellar contract build --verifiable` — a reproducible build inside a +digest-pinned container that stamps the SEP-58 fields into the Wasm — and PR +[#2586](https://github.com/stellar/stellar-cli/pull/2586) adds `stellar +contract verify`, which re-runs the recorded build locally and byte-compares. +Those commands are deliberately local and one-to-one: every verifier pays the +full rebuild cost, and no verdict persists for anyone else. **Our service is +the shared aggregation layer above that CLI capability, not a competitor to +it**: a contract built with `--verifiable` is submittable to +`POST /v1/verifications` with zero additional metadata (the service reads the +SEP-58 fields straight out of `contractmetav0`), the hosted rebuild runs in +the same digest-pinned image the CLI recorded, and the resulting signed +verdict is then queryable by every explorer and wallet — once, for everyone, +instead of once per consumer. `stellar contract verify` remains the +trust-minimized local fallback for anyone who prefers not to trust any +verifier at all, which is exactly the relationship Sourcify-style services +have with local compiler runs on other chains. **We commit to supporting +whichever CLI↔service interaction shape SDF names** — service-mediated +submission (the CLI posts to a verifier and polls) or on-chain result +discovery (the CLI reads verdicts from where the ecosystem publishes them) — +and will implement the corresponding CLI integration PRs as part of milestone +M4. + +**Web submission.** A web UI covers developers not working from the CLI: +paste a contract ID, the service reads `contractmetav0`, pre-fills the SEP-58 +fields, and lets the submitter confirm or supply the source mode inputs. The +same form accepts a tarball upload for modes 2 and 3. + +**Retroactive verification — a stated RFP priority we treat as such.** +Contracts deployed before SEP-58 tooling existed have no embedded metadata, +and non-upgradable contracts can never add it. For these, `POST +/v1/verifications` accepts the full SEP-58 field set **in the request body as +an off-chain metadata submission**: the submitter asserts "this Wasm hash was +built from this source in this image with these options," and the service +tests the assertion by rebuilding. The verdict is exactly as strong as in the +embedded case — the rebuild either reproduces the deployed bytes or it does +not; the embedded fields were never trusted, only tested. The response +records that the claim arrived off-chain (`claim_channel: +"offchain-submission"` vs `"contractmetav0"`) so consumers can distinguish +the provenance of the *claim* while relying identically on the *evidence*. +This is how the large existing population of deployed Soroban contracts — +including every non-upgradable one — becomes verifiable without redeployment. + +**Docs-to-verified in under 15 minutes.** We ship and continuously test a +quickstart walkthrough with an explicit budget: from landing on the docs to a +green `FULL_MATCH` on a testnet contract in under 15 minutes, on the path +`stellar contract build --verifiable` → deploy → submit (one CLI command or +one form) → query. The MVP repo's verification loop (build, deploy, verify +the fixture) already runs in well under that budget locally; the grant work +keeps the hosted path inside it, and the walkthrough is exercised in CI +against the live testnet deployment so regressions in the developer +experience fail a build rather than a first impression. + +## 8. Security & threat model + +A verification service invites adversarial input by design: its core +operation is "execute a stranger's build." The threat model and mitigations: + +**Malicious build execution.** A Cargo build runs arbitrary code +(`build.rs`, proc-macros). Every rebuild therefore executes in an ephemeral +container, pulled by digest, with **no network access during compilation** — +`--network=none` is already how the MVP's Docker builds run, demonstrated in +`scripts/verify.sh`, not an aspiration. Source acquisition (clone/download) +happens in a separate stage from compilation, so the build itself can never +exfiltrate or fetch. Containers run as an unprivileged user with CPU, memory, +disk, and wall-clock limits; a build that exceeds them is killed and recorded +as `ERROR` with the resource cause. + +**Secret exfiltration.** Build containers carry no secrets to steal: no +service credentials, no signing keys, no cloud metadata access (the metadata +endpoint is blocked at the network layer, which `--network=none` subsumes +during compile). The verifier's ed25519 signing key lives only in the signing +service, which consumes build *outputs* (hashes and verdicts) and never +executes submitted code. + +**Cross-submission contamination.** Each job gets a fresh container and a +fresh workspace; nothing writable is shared between jobs. Dependency caches, +if used for performance, are content-addressed and mounted read-only — a +poisoned artifact cannot enter the cache because entries are keyed by the +digest of what they claim to be. + +**Tarball and artifact integrity.** Downloaded tarballs are hashed and +checked against `tarball_sha256` before extraction; extraction guards against +path traversal and zip-bomb expansion (size and entry-count limits). The +artifact store is append-only and content-addressed (§3), so stored-artifact +tampering is detectable by any reader. + +**Result integrity.** Every result is ed25519-signed over its canonical +encoding (§5); the registry is append-only, and trust-tier changes append +history rather than rewriting records (§4). Compromise of the API serving +layer can therefore deny service but cannot forge verdicts that verify +against the published keys. + +**DoS and abuse on write endpoints.** `POST /v1/verifications` is rate-limited +per source address, deduplicated by (wasm hash, source artifact digest, image +digest) so identical re-submissions return the existing job instead of new +work, capped by global queue depth with honest `429`/queue-position +responses, and bounded per-job by the resource limits above. Read endpoints +are cacheable (results are immutable once signed) and fronted by a CDN, so +query-side load does not touch the build infrastructure at all. + +**Third-party audit before production.** We will undergo a security audit +coordinated by SDF through the **audit bank** before the production/mainnet +launch, scoped to the rebuild sandbox, the signing and registry layer, and +the API; the audit report and resolved findings are a milestone deliverable +(§11), and the threat model above is maintained as a living document in the +repository for the auditors to attack. + +## 9. Operations & sustainability + +**Availability.** The query API targets **99%+ uptime**, achieved by +separating the read path from the build path: reads are served from a +replicated database behind stateless API instances and a CDN (signed results +are immutable, hence aggressively cacheable), so build-side incidents cannot +take down queries. The write path degrades gracefully — if workers are down, +submissions queue and report status honestly. + +**Latency.** Verification of standard-size contracts **completes within 5 +minutes, or the API returns an explicit queued status** — `POST +/v1/verifications` answers `202` immediately with a job resource exposing +state (`queued` → `building` → `done`), queue position, and a retry hint, so +integrators never hang on a long poll. The MVP fixture rebuilds in well under +a minute; the five-minute budget covers realistic dependency-heavy contracts, +and per-toolchain warm image pulls plus read-only dependency caches (§8) keep +the common case far below it. + +**Monitoring, runbook, on-call.** The deployment ships with metrics +(API availability and latency percentiles, queue depth, build duration +percentiles, verdict-rate anomalies — a spike in `NO_MATCH` across many +contracts is an early non-determinism alarm), alerting on SLO burn, a public +status page, and an operational runbook covering the failure modes we can +enumerate (RPC outages, image-registry outages, queue backpressure, allowlist +rollback). Bleu staffs an on-call rotation for the production service through +the grant period and the post-grant tail. + +**Retention and egress.** Verification records are retained indefinitely — +they are small, append-only, and their value compounds. Source artifacts are +retained for the life of the service and pinned to IPFS where licensing +permits, with the IPFS CID in the record so the ecosystem can co-pin; mode-3 +artifacts are retained only with submitter consent (§3). **Egress costs for +the public API and artifact downloads are owned by Bleu** within the grant +and tail period, bounded by CDN caching and by serving large artifacts +preferentially via IPFS; the runbook documents the cost model so any future +operator inherits a known bill, not a surprise. + +**Post-grant ownership and funding tail.** Bleu commits to operating the +hosted service for at least **12 months beyond the final grant milestone** at +our own cost, while working toward the service's long-term home: because the +codebase is self-hostable and results are portable signed statements, the +hosted instance is replaceable by design (§5), and we will actively support +ecosystem operators (explorers, SDF, community) standing up peer instances — +the healthiest end state is one where our instance is one of several. Ongoing +maintenance economics (toolchain image updates, SEP conformance work) are +deliberately small: the heavy costs are bounded by caching and IPFS, and we +will propose follow-on community funding only if peer adoption has not +materialized by the end of the tail. + +## 10. Prior art & approach justification + +We reviewed the relevant public repositories rather than reasoning from +memory; the observations below are from the projects' current public code and +documentation, and they jointly justify a *build* (with heavy pattern reuse) +rather than an *adapt* of any single codebase. + +**Sourcify ([github.com/ethereum/sourcify](https://github.com/ethereum/sourcify)).** +Sourcify is the proof that an open, shared verification layer works at +ecosystem scale, and we adopt several of its patterns directly: the open-data +repository of verified contracts; the async job API (`POST +/v2/verify/{chainId}/{address}` returning a `verificationId` to poll, then +`GET /v2/contract/{chainId}/{address}` for results — the same +submit/poll/lookup shape as our §6); a monitor service that watches chains +and proactively verifies new deployments; and the graded verdict vocabulary +("exact match" vs "match", formerly full/partial). But Sourcify's matching +mechanism cannot be adapted to Soroban, because it rests on two EVM-specific +facts: solc embeds a CBOR-encoded metadata hash *inside the deployed +bytecode* that transitively commits to the source files (Sourcify's own docs: +"Change a byte in the source code → Source code hash changes → Metadata +changes → Metadata hash changes → Deployed bytecode changes"), and +compilation is a pure function of a stdJSON input plus a single versioned +compiler binary — so Sourcify never needs a pinned *environment*, just a +pinned solc. Soroban Wasm has no compiler-enforced source commitment +(SEP-46 `contractmetav0` fields are self-declared), and a cargo build's +output depends on the whole toolchain environment. Hence our approach: +environment-pinned full rebuilds in digest-addressed images, with the +`METADATA_ONLY_MATCH` verdict as the Soroban analogue of Sourcify's partial +match (the one part of the gradient that does transfer, since `contractmetav0` +is a strippable custom section — already implemented in the MVP). + +**solana-verifiable-build / solana-verify +([github.com/Ellipsis-Labs/solana-verifiable-build](https://github.com/Ellipsis-Labs/solana-verifiable-build)).** +The closest architectural template, and validation that digest-pinned Docker +rebuilds are the right primitive for toolchain-sensitive targets: build +images are selected by pinned digest, installer scripts in generated +Dockerfiles are checksum-pinned, and the README is explicit that verified +builds "should not be considered a complete security solution" — +source↔binary correspondence, not code safety, which is our framing too. Its +trust split is instructive: build parameters (repo, commit, args) are +published on-chain in a PDA signed by the program's *upgrade authority* (the +claim channel), while a remote verifier — OtterSec's API at `verify.osec.io`, +reached via `solana-verify remote submit-job` — re-runs the build and +publishes the verdict explorers consume (the evidence channel), with local +`verify-from-repo` always available as the trust-nothing fallback. We keep +that claim/evidence split but improve on two known weaknesses: Stellar's +claim channel is SEP-58 metadata embedded in the Wasm itself (no separate +on-chain registry program to deploy and trust), and where Solana's ecosystem +effectively trusts a single hosted verifier, our §5 architecture makes +multiple signing verifiers and per-verifier disagreement rendering the +day-one design rather than a retrofit. + +**Stellar Expert's build workflow and the SDF prototype landscape.** The RFP +context names an SDF experimental prototype; in our review we found no public +repository named `stellar-experimental/contract-verifications` (the obvious +URLs 404), and the substantive existing Soroban prior art is +**[stellar-expert/soroban-build-workflow](https://github.com/stellar-expert/soroban-build-workflow)** +(OrbitLens), the reusable GitHub Actions workflow behind stellar.expert's +contract validation and the origin of what became SEP-55. It compiles +contracts in CI, publishes releases with the Wasm and its SHA-256, and +"generates and uploads build attestations" (GitHub artifact attestations); +validation checks the attestation's `runDetails.builder.id` against the +trusted workflow path. Three observations from its README and the SEP-55 +discussion ([stellar discussion +#1573](https://github.com/orgs/stellar/discussions/1573)) shaped this +proposal. First, the trust chain is contract → GitHub attestation → GitHub +Actions runner: nobody independently rebuilds, and discussion participants +noted attestations don't prevent build-time injection. Second, it is +single-surface: StellarExpert is effectively the lone consumer/validator, and +GitHub is a single point of failure (Rekor transparency logs, IPFS, and +GitLab support were raised in the discussion but not implemented). Third — +and most telling — the README itself warns that contracts must be deployed +*directly from the workflow's release artifacts* "otherwise the deployed +contract hash may not match the release artifacts due to compilation +environment variations." That is the workflow acknowledging exactly the gap a +rebuild service fills: Soroban builds are environment-sensitive, so a +verification layer must pin the environment and re-execute, not attest. None +of this makes SEP-55 inferior — it answers the provenance question cheaply +and instantly, and we surface its attestations as a first-class field (§4) — +but it is why the rebuild layer needs to exist as well. + +**stellar-cli PRs [#2585](https://github.com/stellar/stellar-cli/pull/2585) +and [#2586](https://github.com/stellar/stellar-cli/pull/2586)** (both open +and unmerged at the time of writing) supply the local half of the SEP-58 +story: `build --verifiable` rejects tag-only image references (digest-pinned +`--image` required), implies `--locked`, requires a clean git tree, and +stamps `bldimg`, `bldopt`, and source identification into the Wasm; `contract +verify` reads the embedded metadata, re-runs the recorded build, and +byte-compares, with a trust prompt before pulling unrecognized images and +tarball sources "never default-trusted." What the CLI pair deliberately does +not provide — persistent verdicts, a queryable registry, badges, bulk and +retroactive verification, multi-verifier aggregation — is precisely this +service (§7). + +**Why build rather than adapt:** Sourcify's core is EVM-metadata-specific; +solana-verify's claim channel is a Solana program and its service half +(OtterSec's backend) is not the open-source part; the SEP-55 workflow is an +attestation producer, not a rebuild verifier. Meanwhile the genuinely +chain-specific pieces we need — RPC chain reading, `contractmetav0` parsing, +verdict logic, the pinned rebuild container — are already written and tested +in our MVP repo. We therefore build the service on our existing Soroban-native +foundation while deliberately importing the proven patterns: Sourcify's open +data and API shape, solana-verify's digest-pinned rebuild discipline and +claim/evidence split, and SEP-55 coexistence as designed by SEP-58 itself. + +## 11. Milestones + +Milestones are mapped directly to the RFP's deliverables list. Each milestone +has an objective completion test. + +**M1 — Audit-ready service codebase.** The open-source, self-hostable +codebase (RFP deliverable 1) feature-complete for audit: multi-verifier +signing (§5), allowlist enforcement with trust tiers and downgrade semantics +(§4), all three source modes with IPFS retrieval and the tamper-evident +artifact store (§3), sandboxed rebuild workers (§8), and the written threat +model. *Done when:* a third party can clone the repo, stand up a full +verifier instance from docs alone, and verify the MVP fixture end to end; +audit scope agreed with the SDF audit bank. + +**M2 — Security audit and hosted deployment.** Third-party audit through the +SDF audit bank with the report published and findings resolved (RFP +deliverable: audit report and resolved findings); the hosted service deployed +publicly against **testnet and mainnet** (RFP deliverable: public +deployment), including the mainnet chain-reader configuration and the +retroactive submission path. *Done when:* the audit report and remediations +are public, and `GET /v1/contract/{id}` answers for both networks on the +production endpoint. + +**M3 — Stable public API, client SDK, and documentation.** The `/v1` API +frozen and documented with OpenAPI (RFP deliverable: stable documented API); +the client SDK/reference client published, with the explicit conformance +commitment to the forthcoming verifier-API SEP (RFP deliverable: SDK/client +conforming to the verifier-API SEP) — implemented against the SEP if it has +been authored by this milestone, otherwise against `/v1` with the SEP +migration tracked as a standing commitment (§6); the image allowlist policy +and governance process published (RFP deliverable: allowlist policy +documentation). *Done when:* an integrator can go from the docs to rendering +verification state without contacting us, and the under-15-minute walkthrough +passes in CI against the live deployment. + +**M4 — Integrations.** The reference integrations of §12 shipped (badge +endpoint, explorer embed, SDK example) plus integration documentation (RFP +deliverable: integration docs) and at least one reference integration landed +with Stellar Lab or a cooperating verifier/explorer (RFP deliverable: +reference integration), alongside the CLI interaction work in whichever shape +SDF names (§7). *Done when:* a named partner surface renders our verification +results in production for testnet and mainnet contracts. + +**M5 — Production handoff and sustainability.** The operational runbook +published (RFP deliverable: operational runbook); monitoring, status page, +and on-call in steady state (§9); retention/egress cost model documented; the +12-month post-grant operating tail formally begun, including the peer-operator +support program (§9). *Done when:* SDF accepts the runbook, the SLO dashboards +are public, and at least one external party has a peer verifier instance +running or in progress. + +## 12. Integrations plan + +The service is only useful where users already look, so reference +integrations are deliverables, not aspirations: + +- **Badge endpoint** — `GET /v1/badge/{contractId}.svg`: a cacheable SVG + rendering the verdict and image trust tier, designed for READMEs and + explorer pages, with per-verifier variants reflecting §5's disagreement + rendering. +- **Explorer embed snippet** — a documented JSON payload plus a small, + dependency-free web component an explorer can drop in to render the full + per-verifier result set (verdicts, trust tiers, SEP-55 presence, source + links) from one API call. +- **Client SDK example** — a TypeScript client (the natural extension of the + MVP's reader package) wrapping the four endpoints, with the trusted-verifier + policy surface built in and a worked example that resolves a contract ID to + a render-ready verification summary. + +During the grant we will engage the partners the RFP context names — +**OrbitLens / Stellar Expert** (whose build workflow pioneered Soroban +verification UX and whose explorer is its most prominent surface), **Aha Labs +/ rgstry.xyz**, **57B**, and **Stellar Lab** (whose Contract Explorer +currently displays SEP-55 attestation state and is the natural first surface +for showing the rebuild verdict alongside it) — to land the M4 reference +integration and to make sure the embed and SDK fit how their products +actually consume data, rather than how we imagine they do. Stellar Expert +specifically is a natural early *peer verifier* candidate, not just a +consumer, given OrbitLens's role in originating SEP-55. + +## 13. Compliance & openness + +The service is operated as a public good, and the RFP's compliance +requirements are properties of the design rather than policies bolted on: + +- **No KYC, no gated access.** Read endpoints are anonymous and free; + submission requires no account — anti-abuse is handled by rate limits and + resource caps (§8), not identity. Nothing about a submitter is collected + beyond what operating the service requires (transient rate-limit state). +- **Apache-2.0, everything.** The verifier, API, workers, SDK, badge + rendering, deployment configuration, and documentation are all in the + public repository under Apache-2.0 — the license the MVP repo already + carries. +- **Self-hostable by construction.** A single documented deployment brings up + a complete verifier instance; M1's completion test is literally that a third + party can do this from docs alone. No closed dependencies, no privileged + signing authority, no calls home. +- **Community-operable over time.** The multi-verifier design (§5) means the + ecosystem can outgrow us without migration: results are portable signed + statements, peer instances are first-class from day one, and the post-grant + plan (§9) actively works toward a steady state where the hosted instance we + run is one among several. The healthiest possible outcome of this grant is + an ecosystem that no longer depends on any single operator — including us. + +--- + +## Appendix A — RFP requirements traceability + +| RFP requirement | Where addressed | +|---|---| +| Accept source submissions tied to a target Wasm hash | §7 (CLI, web, retroactive); §6 `POST /v1/verifications` | +| Rebuild in SDF-allowlisted trusted image via SEP-58 `bldimg` | §2 (field mapping); §4 (allowlist, tiers) | +| SEP-58 field consumption (all six fields) | §2 field-by-field table | +| Free, public query API by contract ID or Wasm hash | §6 (`GET /v1/contract/{id}`, `GET /v1/wasm/{hash}`); §13 (no gating) | +| Shared result layer — no per-consumer rebuilds, no single hardcoded verifier | §5 (aggregation, open registration); §7 (vs CLI 1:1 model) | +| Multi-verifier architecture with per-verifier results & disagreement signals | §5 (signing, trusted sets, disagreement rendering) | +| Mainnet and testnet | §2, §11 (M2) | +| Retroactive verification for non-upgradable / pre-launch contracts | §7 (off-chain metadata submission path) | +| All three source modes + IPFS | §3 (modes 1–3; IPFS first-tier) | +| Developer submission flow aligned with stellar-cli | §7 (PRs #2585/#2586 positioning; either interaction shape) | +| Explorer-consumable metadata | §6 (schema), §12 (badge, embed, SDK) | +| SEP-55 vs SEP-58 as distinct trust levels | §4 (complementary framing, distinct API field) | +| Verifier-API SEP conformance | §6 (versioning policy), §11 (M3) | +| Tamper-evident tarball storage; isolated rebuild environment | §3 (content-addressed store); §8 (sandbox, `--network=none`) | +| Third-party security audit before production | §8, §11 (M2, audit bank) | +| Under-15-minute developer experience | §7 (walkthrough with CI-enforced budget) | +| Decentralization | §5, §13 | +| Verification within 5 minutes or queued status | §9 (latency), §6 (`202` + job resource) | +| 99%+ uptime for the query API | §9 (read/build path separation, CDN) | +| Retention and egress ownership | §9 (retention policy, Bleu-owned egress) | +| Post-grant ownership | §9 (12-month tail, peer-operator program) | +| No KYC / no gated access | §13 | +| Open-source, self-hostable | §13, §11 (M1) | From 0527b31ae23d3da035e901ef3a086dc16900f5a0 Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 12 Jun 2026 16:57:54 -0300 Subject: [PATCH 2/4] Drop proposal traceability appendix; align README, manifest, and code comments with the proposal Remove Appendix A from docs/PROPOSAL.md. Update README to point at the proposal, replace the SCF tranche framing and pre-decision API shape with the /v1 endpoints and grant milestones, correct the SEP-55 title, and reference SEP-58. Align toolchain-manifest and reader source comments with the same grant-scope vocabulary. --- README.md | 70 ++++++++++++++++++++-------------- docker/toolchain-manifest.json | 2 +- docs/PROPOSAL.md | 29 -------------- reader/src/networks.ts | 2 +- reader/src/verify.ts | 3 +- 5 files changed, 46 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 4699a4b..8a024ad 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,12 @@ is byte-for-byte the published source — by independently **re-compiling** the source on neutral infrastructure and comparing hashes, not by trusting a build provenance attestation. -This is the SCF RFP **MVP tranche**: a single pinned toolchain, a chain reader, -and a verify-by-contract-ID CLI. Registry API + UI, multi-toolchain selection, -and explorer badge integration are later tranches (see [Roadmap](#roadmap)). +This repo is the working MVP behind our grant proposal — +**[docs/PROPOSAL.md](docs/PROPOSAL.md)** — responding to the SDF RFP "Contract +Source Verification Service": a single pinned toolchain, a chain reader, and a +verify-by-contract-ID CLI. The hosted multi-verifier service, `/v1` public +API, UI, multi-toolchain selection, and explorer integrations are grant scope +(see [Roadmap](#roadmap)). License: **Apache-2.0**. @@ -31,14 +34,16 @@ So source verification reduces to one question: > **Can we rebuild a candidate source repo into a WASM whose SHA-256 equals the > hash the ledger reports for this contract?** -Today's tooling (stellar.expert's build workflow, the in-progress Contract Source -Validation SEP — [SEP-0055], formalized from [discussion #1573]) relies on GitHub -Attestations, which only attest that *a GitHub Action ran and produced a WASM*. -The official Stellar Lab Contract Explorer is explicit that its "Build Verified" -badge "only means that the GitHub Action run has attested to have built the Wasm, -but does not verify the source code." Soroscan Verify adds the **independent, -neutral, reproducible-build** layer those approaches lack — the Soroban analogue -of [Sourcify]'s bytecode-match model for Ethereum (full match vs. partial match). +Today's tooling (stellar.expert's build workflow, the Contract Build +Verification SEP — [SEP-0055], formalized from [discussion #1573]) relies on +GitHub Attestations, which attest that *a GitHub Action ran and produced a +WASM*. The official Stellar Lab Contract Explorer is explicit that its "Build +Verified" badge "only means that the GitHub Action run has attested to have +built the Wasm, but does not verify the source code." Soroscan Verify adds the +complementary **independent, neutral, reproducible-build** layer ([SEP-0058]) +— the Soroban analogue of [Sourcify]'s bytecode-match model for Ethereum (full +match vs. partial match). SEP-55 answers "did trusted CI build this?"; the +rebuild layer answers "does this source produce these bytes?". ## Architecture (data flow) @@ -49,27 +54,28 @@ The diagram uses the SDK method names as they actually exist on ```mermaid flowchart TD - Dev[Developer / Auditor] -->|contract ID or wasm hash + source repo| UI[Verification UI - React/Next - later tranche] - UI -->|POST /verify - later tranche| API[Registry API - TypeScript - later tranche] - API -->|enqueue job| Q[Build Queue - later tranche] + Dev[Developer / Auditor] -->|contract ID or wasm hash + source repo| UI[Verification UI - React/Next - grant scope] + UI -->|POST /v1/verifications - grant scope| API[Public API - /v1 - grant scope] + API -->|enqueue job| Q[Build Queue - grant scope] API -->|fetch on-chain wasm| Reader[Chain Reader - stellar-sdk RPC - MVP] Reader -->|getContractWasmByContractId / getContractWasmByHash| RPC[(Stellar RPC - ContractCodeEntry - SHA-256 hash)] - Reader -->|SEP-46 contractmetav0 - rsver rssdkver source_repo| API + Reader -->|SEP-46 contractmetav0 - SEP-58 bldimg bldopt source_repo source_rev| API Q --> Worker[Reproducible Build Worker - pinned Docker - MVP] Worker -->|pull pinned image by digest| Docker[(Docker image - toolchain - MVP)] - Worker -->|clone repo@commit or local source| Git[(Source - GitHub/GitLab/tarball)] + Worker -->|clone repo@commit or local source| Git[(Source - repo / tarball / content-addressed)] Worker -->|stellar contract build --locked - target wasm32v1-none| WASM[Rebuilt WASM + SHA-256 - MVP] WASM -->|compare hash and section diff| Match{Match verdict - MVP} - Match -->|full / metadata-only / none| DB[(Postgres - verdict repo commit - later tranche)] - DB -->|mirror artifacts| IPFS[(IPFS pin - later tranche)] - DB --> Badge[Badge endpoint - GET /badge/id.svg - later tranche] - Badge --> Explorer[Soroban-First Block Explorer + Stellar Lab Contract Explorer - later tranche] - DB --> Query[GET /verification/wasmHash - GET /contract/id - later tranche] + Match -->|full / metadata-only / none| DB[(Verification registry - ed25519-signed results - grant scope)] + DB -->|mirror artifacts| IPFS[(IPFS pin - grant scope)] + DB --> Badge[Badge endpoint - GET /v1/badge/id.svg - grant scope] + Badge --> Explorer[Explorers - Stellar Expert / Stellar Lab Contract Explorer - grant scope] + DB --> Query[GET /v1/wasm/hash - GET /v1/contract/id - grant scope] Query --> UI ``` Pieces labeled **MVP** are implemented and tested in this repo. Pieces labeled -**later tranche** are scoped but not built here. +**grant scope** are specified in [docs/PROPOSAL.md](docs/PROPOSAL.md) but not +built here. ## Stack (plain English) @@ -200,11 +206,17 @@ test/integration.testnet.test.ts`. ## Roadmap -| Tranche | Scope | -|---------|-------| +Grant milestones are specified in [docs/PROPOSAL.md §11](docs/PROPOSAL.md); +in summary: + +| Milestone | Scope | +|-----------|-------| | **MVP (this repo)** | Single pinned toolchain, chain reader, verify-by-ID CLI, deterministic hash-match proof on testnet. | -| Testnet | Registry API (`POST /verify`, `GET /contract/{id}`, `GET /verification/{wasmHash}`, `GET /badge/{id}.svg`), React/Next UI with diff viewer, multi-toolchain selection via SEP-46 `contractmetav0`, IPFS mirroring. | -| Mainnet | Production hardening, explorer badge integration (Soroban-First Block Explorer; Stellar Lab Contract Explorer fallback), docs + maintenance plan. | +| M1 — Audit-ready codebase | Self-hostable verifier with ed25519 result signing, image allowlist + trust tiers, all three SEP-58 source modes, IPFS retrieval, sandboxed rebuild workers. | +| M2 — Audit + hosted deployment | Security audit via the SDF audit bank; public **testnet + mainnet** deployment; retroactive (off-chain metadata) submission path. | +| M3 — Stable `/v1` API + SDK + docs | `GET /v1/contract/{id}`, `GET /v1/wasm/{hash}`, `POST /v1/verifications`, `GET /v1/verifiers`; client SDK; allowlist policy doc; under-15-minute walkthrough. | +| M4 — Integrations | Badge endpoint (`GET /v1/badge/{id}.svg`), explorer embed, stellar-cli interaction, partner reference integration. | +| M5 — Production handoff | Runbook, monitoring, on-call, 12-month post-grant operating tail. | ## References @@ -213,12 +225,14 @@ test/integration.testnet.test.ts`. - Retrieve a contract code ledger entry (LedgerKeyContractCode / getLedgerEntries) - `@stellar/stellar-sdk` `rpc.Server` API reference (method names) - [SEP-0046] Contract Meta (`contractmetav0` / SCMetaEntry) -- [SEP-0055] Contract Source Validation (formalized; alongside [discussion #1573]) +- [SEP-0055] Contract Build Verification (GitHub-Attestation provenance; alongside [discussion #1573]) +- [SEP-0058] Contract Build Reproducibility for Verification (`bldimg`, `bldopt`, `source_repo`, `source_rev`, `tarball_url`, `tarball_sha256`) - [Sourcify] — Ethereum source verification (full vs partial match) prior art - OpenZeppelin `stellar-contracts` (Rust Soroban library) -- SCF Build Award handbook (tranche model: 10% award / 20% MVP / 30% Testnet / 40% mainnet+UX) +- SDF RFP "Contract Source Verification Service" (Q2 2026) — our response: [docs/PROPOSAL.md](docs/PROPOSAL.md) [SEP-0046]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0046.md [SEP-0055]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0055.md +[SEP-0058]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0058.md [discussion #1573]: https://github.com/orgs/stellar/discussions/1573 [Sourcify]: https://github.com/ethereum/sourcify diff --git a/docker/toolchain-manifest.json b/docker/toolchain-manifest.json index 0b228c6..3a00f08 100644 --- a/docker/toolchain-manifest.json +++ b/docker/toolchain-manifest.json @@ -1,5 +1,5 @@ { - "$comment": "Pinned toolchain manifest for the MVP single-toolchain reproducible-build image. The on-chain WASM hash is reproducible only against THIS exact toolchain. For the Testnet tranche, image selection becomes automatic via SEP-46 contractmetav0 keys (rsver, rssdkver).", + "$comment": "Pinned toolchain manifest for the MVP single-toolchain reproducible-build image. The on-chain WASM hash is reproducible only against THIS exact toolchain. In the grant scope (docs/PROPOSAL.md), image selection is driven by the SEP-58 bldimg digest recorded in contractmetav0 or submitted off-chain.", "image": "soroscan-verify-builder:rust-1.91.1-cli-26.1.0", "baseImage": "rust:1.91.1-bookworm", "baseImageDigest": "TODO: pin via `docker buildx imagetools inspect rust:1.91.1-bookworm` and record the sha256 here", diff --git a/docs/PROPOSAL.md b/docs/PROPOSAL.md index adc91d7..8e8410e 100644 --- a/docs/PROPOSAL.md +++ b/docs/PROPOSAL.md @@ -748,32 +748,3 @@ requirements are properties of the design rather than policies bolted on: run is one among several. The healthiest possible outcome of this grant is an ecosystem that no longer depends on any single operator — including us. ---- - -## Appendix A — RFP requirements traceability - -| RFP requirement | Where addressed | -|---|---| -| Accept source submissions tied to a target Wasm hash | §7 (CLI, web, retroactive); §6 `POST /v1/verifications` | -| Rebuild in SDF-allowlisted trusted image via SEP-58 `bldimg` | §2 (field mapping); §4 (allowlist, tiers) | -| SEP-58 field consumption (all six fields) | §2 field-by-field table | -| Free, public query API by contract ID or Wasm hash | §6 (`GET /v1/contract/{id}`, `GET /v1/wasm/{hash}`); §13 (no gating) | -| Shared result layer — no per-consumer rebuilds, no single hardcoded verifier | §5 (aggregation, open registration); §7 (vs CLI 1:1 model) | -| Multi-verifier architecture with per-verifier results & disagreement signals | §5 (signing, trusted sets, disagreement rendering) | -| Mainnet and testnet | §2, §11 (M2) | -| Retroactive verification for non-upgradable / pre-launch contracts | §7 (off-chain metadata submission path) | -| All three source modes + IPFS | §3 (modes 1–3; IPFS first-tier) | -| Developer submission flow aligned with stellar-cli | §7 (PRs #2585/#2586 positioning; either interaction shape) | -| Explorer-consumable metadata | §6 (schema), §12 (badge, embed, SDK) | -| SEP-55 vs SEP-58 as distinct trust levels | §4 (complementary framing, distinct API field) | -| Verifier-API SEP conformance | §6 (versioning policy), §11 (M3) | -| Tamper-evident tarball storage; isolated rebuild environment | §3 (content-addressed store); §8 (sandbox, `--network=none`) | -| Third-party security audit before production | §8, §11 (M2, audit bank) | -| Under-15-minute developer experience | §7 (walkthrough with CI-enforced budget) | -| Decentralization | §5, §13 | -| Verification within 5 minutes or queued status | §9 (latency), §6 (`202` + job resource) | -| 99%+ uptime for the query API | §9 (read/build path separation, CDN) | -| Retention and egress ownership | §9 (retention policy, Bleu-owned egress) | -| Post-grant ownership | §9 (12-month tail, peer-operator program) | -| No KYC / no gated access | §13 | -| Open-source, self-hostable | §13, §11 (M1) | diff --git a/reader/src/networks.ts b/reader/src/networks.ts index bed642d..adcc610 100644 --- a/reader/src/networks.ts +++ b/reader/src/networks.ts @@ -2,7 +2,7 @@ * Network configuration. TESTNET ONLY for this MVP. * * Mainnet config is intentionally NOT wired up. Adding mainnet here is a - * deliberate, reviewed step (see milestones: mainnet is a later tranche). + * deliberate, reviewed step (grant milestone M2; see docs/PROPOSAL.md). */ export interface NetworkConfig { name: string; diff --git a/reader/src/verify.ts b/reader/src/verify.ts index 33d38fe..73712e8 100644 --- a/reader/src/verify.ts +++ b/reader/src/verify.ts @@ -12,7 +12,8 @@ import { extractContractMetaSection } from "./contractmeta.js"; * - METADATA_ONLY_MATCH: WASM differs ONLY in the `contractmetav0` custom * section (behaviorally identical). Sourcify "partial * match" analogue. (Detected structurally in the MVP; - * a strict XDR section-diff is a Testnet-tranche item.) + * a strict XDR section-diff is grant scope; see + * docs/PROPOSAL.md.) * - NO_MATCH: hashes differ and the difference is not metadata-only. * - ERROR: could not fetch/compare (network, bad ID, etc.). */ From 93385209524fde2de574226b520e14f42bb9599f Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 12 Jun 2026 17:02:47 -0300 Subject: [PATCH 3/4] Tighten the proposal: cut cross-section repetition, plainer prose Same 13 sections, decisions, and RFP coverage; the multi-verifier, IPFS/artifact-store, network-isolation, and SEP-55 points now each live in one section instead of three or four. --- docs/PROPOSAL.md | 1045 +++++++++++++++++++--------------------------- 1 file changed, 427 insertions(+), 618 deletions(-) diff --git a/docs/PROPOSAL.md b/docs/PROPOSAL.md index 8e8410e..39e8039 100644 --- a/docs/PROPOSAL.md +++ b/docs/PROPOSAL.md @@ -4,91 +4,72 @@ - **Applicant:** Bleu (bleu.studio) - **Repository:** `scf-contract-source-verification` (Apache-2.0, public) -- **Working MVP:** chain reader, verify-by-contract-ID CLI, pinned Docker build toolchain, live testnet fixture with a deterministic on-chain hash match +- **Working MVP:** chain reader, verify-by-contract-ID CLI, pinned Docker build toolchain, live testnet fixture with a matching on-chain hash - **Date:** June 2026 --- ## 1. Summary & current state -Soroban contracts are deployed as opaque Wasm blobs. The ledger stores uploaded -bytecode in a `ContractCodeEntry` keyed by the SHA-256 of the executable, and a -deployed instance references that code by hash. Source verification therefore -reduces to a single falsifiable question: *can a candidate source tree be -rebuilt into a Wasm whose SHA-256 equals the hash the ledger reports for this -contract?* This proposal describes a hosted, free, public verification service -that answers that question at ecosystem scale: it consumes the SEP-58 metadata -vocabulary (`bldimg`, `bldopt`, `source_repo`, `source_rev`, `tarball_url`, -`tarball_sha256`), rebuilds source inside SDF-allowlisted trusted build images, -byte-compares the result against deployed Wasm, and serves signed verification -results to explorers, wallets, and the Stellar CLI through a stable, versioned -API — so the ecosystem gets one shared result layer instead of every consumer -re-running rebuilds or trusting a single hardcoded verifier. - -We are not proposing to start from zero. The public MVP repository already -proves the core verification primitive end to end: - -- **A deterministic rebuild matches the chain.** A sample Soroban contract - (`contracts/hello-soroban`, soroban-sdk 25.3.1, target `wasm32v1-none`) is - deployed on testnet as contract - `CDVSGPL3HFBGJ6ZEYQUAVE3OH3XE2ZE5ZT2GWPA3LKOYVD4UBPQJ2VHB`. Rebuilding its - source with the pinned toolchain (`stellar contract build --locked`, rustc - 1.91.1, stellar-cli 26.1.0) produces a 1,060-byte Wasm whose SHA-256 — - `6fe7bd58e5a33dc27daefc74acfae6eb70f101fdbde860475cf18fde87288e4b` — equals - (a) the hash the CLI prints at upload, (b) the hash the ledger stores in the - `ContractCodeEntry` (confirmed via `stellar contract fetch`), and (c) the - hash our chain reader computes from the Wasm it pulls over RPC. Two clean - rebuilds on the same toolchain produce the identical hash: determinism is - demonstrated, not assumed. -- **A chain reader over Stellar RPC.** `reader/src/chain-reader.ts` fetches - on-chain Wasm and computes its SHA-256 by contract ID - (`getContractWasmByContractId`) or directly by Wasm hash - (`getContractWasmByHash`) using `@stellar/stellar-sdk`'s `rpc.Server` — the - by-hash path matters because one verified hash covers every contract - instance that references it. -- **A verify-by-ID CLI with a graded verdict model.** `soroscan-verify verify - --id --wasm ` compares the rebuilt artifact against the - chain and returns one of four verdicts: `FULL_MATCH` (byte-identical), - `METADATA_ONLY_MATCH` (differs only in the SEP-46 `contractmetav0` custom - section — detected structurally by parsing the Wasm section table and - re-comparing with that section stripped), `NO_MATCH`, or `ERROR`. The CLI - exits 0 only on `FULL_MATCH`, making it scriptable in CI. -- **A pinned, network-isolated build environment.** `docker/Dockerfile` pins - rustc/cargo, the stellar-cli version, and the `wasm32v1-none` target, and the - rebuild runs with `--network=none` — compile-time network isolation is - already demonstrated, not planned. `docker/toolchain-manifest.json` records - the resolved toolchain so a third party can re-derive the same hash from the - same image. - -The grant funds the path from this proven primitive to the service the RFP -asks for: a multi-verifier result layer with signed verdicts, all three SEP-58 -source modes, an SDF-anchored image allowlist with explicit trust tiers, a -free public query API, retroactive verification for already-deployed -contracts, explorer and wallet integrations, a third-party security audit -through the SDF audit bank, and a production operations posture. +Soroban contracts are deployed as opaque Wasm blobs, stored on the ledger +under the SHA-256 of the bytecode. Source verification is therefore one +testable question: *can this source be rebuilt into a Wasm whose SHA-256 +equals the hash on the ledger?* + +We propose a hosted, free, public service that answers that question for the +whole ecosystem. It reads the SEP-58 metadata fields (`bldimg`, `bldopt`, +`source_repo`, `source_rev`, `tarball_url`, `tarball_sha256`), rebuilds the +source inside SDF-allowlisted build images, byte-compares the result against +the deployed Wasm, and serves signed results to explorers, wallets, and the +Stellar CLI through a versioned API. One shared result layer — no per-consumer +rebuilds, no single hardcoded verifier. + +The core of this is already built and public. Our MVP repo proves: + +- **A deterministic rebuild matches the chain.** A sample contract + (soroban-sdk 25.3.1, target `wasm32v1-none`) is live on testnet as + `CDVSGPL3HFBGJ6ZEYQUAVE3OH3XE2ZE5ZT2GWPA3LKOYVD4UBPQJ2VHB`. Rebuilding it + with the pinned toolchain (`stellar contract build --locked`, rustc 1.91.1, + stellar-cli 26.1.0) yields a Wasm whose SHA-256, + `6fe7bd58e5a33dc27daefc74acfae6eb70f101fdbde860475cf18fde87288e4b`, equals + the hash printed at upload, the hash stored on the ledger, and the hash our + reader computes from the Wasm it fetches over RPC. Two clean rebuilds give + the same hash. +- **A chain reader over Stellar RPC** (`reader/src/chain-reader.ts`): fetches + on-chain Wasm by contract ID or directly by Wasm hash — one verified hash + covers every contract instance that shares the bytecode. +- **A verify CLI with a graded verdict model**: `FULL_MATCH` + (byte-identical), `METADATA_ONLY_MATCH` (differs only in the SEP-46 + `contractmetav0` custom section, detected by stripping that section and + re-comparing), `NO_MATCH`, or `ERROR`. Exit code 0 only on `FULL_MATCH`, so + it works in CI. +- **A pinned, network-isolated build environment** (`docker/Dockerfile`): + rustc, stellar-cli, and the Wasm target pinned; the build runs with + `--network=none`. The resolved toolchain is recorded in + `docker/toolchain-manifest.json`. + +The grant funds the path from this primitive to the full service: signed +multi-verifier results, all three SEP-58 source modes, the image allowlist +with trust tiers, the public API, retroactive verification, integrations, a +third-party audit, and production operations. ## 2. Architecture & SEP-58 alignment -SEP-58 ("Contract Build Reproducibility for Verification", draft by Leigh -McCulloch) defines a storage-independent vocabulary that lets anyone re-run a -contract's build and compare the output to the deployed bytes. The service is -architected as a direct consumer of that vocabulary: every field in SEP-58 -drives a specific, auditable pipeline step, and nothing in the pipeline -depends on metadata outside the SEP. Canonically the fields live in the Wasm's -`contractmetav0` custom section (per SEP-46); the service also accepts the -same fields through off-chain submission for contracts deployed before the -tooling existed (§7). - -| SEP-58 field | Semantics (per the SEP) | Pipeline step it drives | -|---|---|---| -| `bldimg` | Fully-qualified container image, **pinned by digest** | Image resolution: the digest is checked against the allowlist (§4), assigned a trust tier, pulled by digest — never by tag — and used as the rebuild environment. Tag-only references are rejected at intake. | -| `bldopt` | One shell-style flag per entry, passed verbatim as a single argument | Build invocation: each recorded flag is appended, verbatim and in order, to the `stellar contract build` command inside the container. No flag interpolation or shell evaluation occurs on the host. | -| `source_repo` | HTTPS URL of the source repository | Source acquisition, mode 1: clone the repository (no system-git dependency in the worker). | -| `source_rev` | Full 40-character commit SHA-1 | Source acquisition, mode 1: check out exactly this commit; branch or tag names are not accepted as substitutes. | -| `tarball_url` | URL where the source tarball can be downloaded | Source acquisition, mode 2: download the tarball over HTTPS or IPFS (§3). | -| `tarball_sha256` | SHA-256 of the tarball bytes | Integrity gate and content address: the downloaded bytes are hashed and must equal this value before extraction (mode 2); alone, it is the lookup key for content-addressed private source (mode 3). | +SEP-58 ("Contract Build Reproducibility for Verification") defines the +vocabulary that lets anyone re-run a contract's build and compare the output +to the deployed bytes. Every field drives one pipeline step, and the pipeline +needs nothing outside the SEP. The fields normally live in the Wasm's +`contractmetav0` custom section (SEP-46); we also accept them via off-chain +submission for contracts deployed before the tooling existed (§7). -The end-to-end flow: +| SEP-58 field | Meaning | What the pipeline does with it | +|---|---|---| +| `bldimg` | Container image, **pinned by digest** | Checked against the allowlist (§4), assigned a trust tier, pulled by digest — never by tag. Tag-only references are rejected. | +| `bldopt` | One build flag per entry, passed verbatim | Each flag is appended, in order, to `stellar contract build` inside the container. Nothing is shell-evaluated on the host. | +| `source_repo` | HTTPS URL of the source repository | Mode-1 source acquisition: clone the repo. | +| `source_rev` | Full 40-char commit SHA-1 | Check out exactly this commit; branch and tag names are not accepted. | +| `tarball_url` | Where to download the source tarball | Mode-2 acquisition, over HTTPS or IPFS (§3). | +| `tarball_sha256` | SHA-256 of the tarball | Integrity check before extraction (mode 2); on its own, the lookup key for private source (mode 3). | ```mermaid flowchart TD @@ -104,227 +85,162 @@ flowchart TD Retro --> Queue API -->|enqueue job| Queue[Build queue] Queue --> Acquire[Source acquisition - repo@rev, tarball, content-addressed] - Acquire --> Store[(Tamper-evident content-addressed artifact store + IPFS pin)] + Acquire --> Store[(Content-addressed artifact store + IPFS pin)] Acquire --> Allow{bldimg digest vs allowlist} - Allow -->|trust tier recorded| Worker[Rebuild worker - pulled by digest, network-isolated, ephemeral] + Allow -->|trust tier recorded| Worker[Rebuild worker - ephemeral, network-isolated] Worker -->|stellar contract build + recorded bldopt| Rebuilt[Rebuilt Wasm + SHA-256] Rebuilt --> Compare{Byte compare vs on-chain Wasm} - Compare -->|FULL_MATCH / METADATA_ONLY_MATCH / NO_MATCH / ERROR| Sign[ed25519-signed verification result] + Compare -->|verdict| Sign[ed25519-signed verification result] Sign --> DB[(Verification registry - append-only)] Peers[Other self-hosted verifiers] -->|signed results| DB DB --> API ``` -Every box above the registry already has a working ancestor in the MVP repo: -the chain reader, the comparison and verdict logic, and the pinned -network-isolated rebuild container are running code; the queue, registry, -signing layer, and public API are the grant-funded build-out. The service -operates against **both mainnet and testnet** from launch — the MVP's network -resolver is testnet-only by deliberate guard, and lifting that guard plus -mainnet RPC configuration is part of milestone M2 (§11). - -A deliberate architectural property: verification is keyed by **Wasm hash**, -not contract ID. The contract-ID endpoint resolves the instance's current code -reference and joins it to verifications of that hash, so one successful -verification covers every instance sharing the bytecode, and contract upgrades -(re-pointing to a new hash) naturally surface as "this instance's current code -is unverified" rather than stale green badges. +The chain reader, verdict logic, and pinned rebuild container are running MVP +code; the queue, registry, signing layer, and API are the grant work. The +service runs against **both mainnet and testnet** from launch (the MVP's +testnet-only guard is lifted in milestone M2). + +Verification is keyed by **Wasm hash**, not contract ID. The contract-ID +endpoint resolves the instance's current code hash and joins to verifications +of that hash. So one verification covers every instance sharing the bytecode, +and a contract upgrade simply shows up as "current code unverified" — no +stale badges. ## 3. Source modes -The service supports all three SEP-58 source modes as first-class citizens — -none is an afterthought, because each serves a real publishing posture in the -ecosystem. - -**Mode 1 — public repository (`source_repo` + `source_rev`).** The worker -clones the repository and checks out the exact recorded commit. Because a git -commit SHA-1 is not a content commitment over the *built tree* with the -strength we want for long-term records, the worker then produces a canonical -source tarball of the checked-out tree and records its SHA-256 — so every -verification, regardless of mode, ends up anchored to a content-addressed -source artifact. If the repository later disappears or rewrites history, the -verification record still points at an artifact we (and IPFS) hold. - -**Mode 2 — hosted tarball (`tarball_url` + `tarball_sha256`).** The service -downloads the tarball and verifies its SHA-256 against the recorded value -before extraction; a mismatch is a hard failure recorded as `ERROR` with a -machine-readable cause, never a silent fallback. **IPFS is a first-tier -retrieval channel alongside HTTPS**: `tarball_url` may be an `ipfs://` URI (or -a gateway URL), and for HTTPS-hosted tarballs the service pins a copy to IPFS -after integrity-checking it, then records the CID in the verification result. -This gives every verified contract a retrieval path that does not depend on -the original host staying alive. - -**Mode 3 — content-addressed private source (`tarball_sha256` alone).** This -mode exists for teams whose source is not public but who still want -independent verification — typically auditor-mediated: the team provides the -tarball directly to one or more verifiers (or to an auditor operating a -verifier instance), each of which checks the hash, rebuilds, and publishes a -signed verdict *without republishing the source*. The public record then says: -"source with digest X, rebuilt in image Y, produces the deployed bytes — -attested by verifiers A and B." Consumers learn exactly as much as the -publisher chose to reveal, and the content address means any future disclosure -of the tarball is mechanically checkable against the original claim. The -multi-verifier architecture (§5) is what makes this mode meaningful: several -independent parties can corroborate a rebuild of source the public cannot see. - -**Tamper-evident artifact storage.** All source artifacts — canonical tarballs -from mode 1, downloaded tarballs from mode 2, auditor-submitted tarballs from -mode 3 where the submitter permits retention — are stored content-addressed by -their SHA-256 in an append-only store, mirrored to IPFS where licensing -permits. An artifact can be added but never mutated in place; the digest in -the signed verification result is the integrity check, so tampering with -stored bytes is detectable by any reader recomputing the hash. This satisfies -the RFP's tamper-evidence requirement structurally rather than procedurally. +All three SEP-58 source modes are supported as equals. + +**Mode 1 — public repo (`source_repo` + `source_rev`).** Clone, check out the +exact commit, build. The worker also packs the checked-out tree into a +canonical tarball and records its SHA-256, so every verification — whatever +the mode — is anchored to a content-addressed source artifact. If the repo +later disappears or rewrites history, the record still points at bytes we and +IPFS hold. + +**Mode 2 — hosted tarball (`tarball_url` + `tarball_sha256`).** Download, +check the SHA-256, extract. A mismatch is a hard `ERROR`, never a silent +fallback. **IPFS is a first-tier retrieval channel alongside HTTPS**: +`tarball_url` may be an `ipfs://` URI, and HTTPS-hosted tarballs are pinned to +IPFS after the integrity check, with the CID recorded in the result — so +verified source stays retrievable even if the original host dies. + +**Mode 3 — content-addressed private source (`tarball_sha256` alone).** For +teams whose source is not public. The team hands the tarball directly to one +or more verifiers — typically via an auditor running a verifier instance — +each of which checks the hash, rebuilds, and signs a verdict *without +publishing the source*. The public record says: "source with digest X, built +in image Y, produces the deployed bytes — say verifiers A and B." Any future +disclosure of the tarball can be checked against the digest. The +multi-verifier design (§5) is what makes this credible: several independent +parties can vouch for a rebuild of source the public can't see. + +**Tamper-evident storage.** All retained source artifacts are stored by their +SHA-256 in an append-only store and mirrored to IPFS where licensing permits +(mode-3 artifacts only with the submitter's consent). Artifacts can be added, +never changed; since the digest is in the signed result, anyone can detect +tampering by re-hashing. ## 4. Trust model -A verification result is only as meaningful as the environment that produced -the rebuild. The service makes that trust explicit instead of implicit, along -two axes: *which build image was used* and *what kind of evidence backs the -verdict*. +A verdict is only as meaningful as the environment that produced it, so the +service makes trust explicit on two axes: which build image, and what kind of +evidence. ### Image trust tiers and the allowlist -Every verification records the `bldimg` digest it ran and the **trust tier** -of that digest at evaluation time: - -- **`sdf-trusted`** — the digest appears on the SDF-published allowlist. The - default allowlist is the set of digests SDF publishes for - `stellar/stellar-cli` images from the `stellar-cli-docker` repository, which - is explicitly "compatible as a SEP-58 image for reproducible Stellar - contract builds" and whose own documentation mandates pinning "to a per-arch - single-architecture digest (`@sha256:…`) — it is the only stable reference." - The service will consume SDF's allowlist endpoint directly once it is - available, so allowlist updates require no service release; until then the - allowlist is a signed, versioned configuration file seeded from the - published `stellar-cli-docker` digests. +Every verification records the trust tier of its `bldimg` digest: + +- **`sdf-trusted`** — the digest is on the SDF-published allowlist. The + default allowlist is the digests SDF publishes for `stellar/stellar-cli` + images from `stellar-cli-docker`, which is explicitly "compatible as a + SEP-58 image" and itself mandates digest pinning ("a per-arch + single-architecture digest (`@sha256:…`) … is the only stable reference"). + We will consume SDF's allowlist endpoint directly once it exists; until + then, a signed config file seeded from the published digests. - **`publicly-auditable`** — a third-party image admitted through a documented - governance process: the image's Dockerfile and build context must be public; - the image must itself be reproducibly buildable from that context; the - digest must be published by the maintainer; and admission requires a public - review window (proposed as a PR against the allowlist repository, open for - comment, with the rationale recorded). This tier exists so the ecosystem is - not bottlenecked on SDF publishing an image for every toolchain combination, - without diluting what `sdf-trusted` means. -- **`arbitrary`** — any other digest-pinned image. The service still performs - the rebuild (the result is real evidence — the bytes matched or they - didn't), but the tier tells consumers the environment itself is unvetted. -- **`unknown`** — the recorded digest can no longer be resolved or its - provenance can no longer be established. - -**Allowlist eviction downgrades; it never deletes.** If a digest is removed -from the allowlist (for example, a vulnerability is found in an image), past -verifications that used it are *not* erased — erasing them would destroy the -historical record and break every consumer that cached a result. Instead, the -trust tier recorded on those verifications is downgraded, the tier-change -event is appended to the verification's history with a timestamp and reason, -and API responses immediately reflect the new tier. Consumers that filter on -`sdf-trusted` see the downgrade at once; consumers that want the full history -can read it. Trust signals age; evidence does not. - -### SEP-55 and SEP-58: two answers to two different questions - -The service treats SEP-55 attestations and SEP-58 rebuild verification as -**complementary, distinct trust levels — neither subsumes the other**, and the -API exposes them as separate fields rather than collapsing them into one -badge. SEP-58's own text says it is "complementary to SEP-55, which uses -signed CI attestations instead of independent rebuild… The two address the -same trust question with different trade-offs," and we adopt exactly that -framing: - -- **SEP-55** ("Contract Build Verification") answers: *did a trusted CI - pipeline build this Wasm from this repository?* The evidence is a GitHub - artifact attestation signed via GitHub's OIDC infrastructure — provenance - from a named builder, available the moment the build runs, with no rebuild - cost. -- **SEP-58 rebuild verification** answers: *does this source, built in this - environment, produce exactly these bytes?* The evidence is an independent - re-execution by a party with no stake in the original build. - -A contract can — and ideally does — carry both: CI provenance for freshness -and a named builder, independent rebuilds for source↔binary correspondence -that doesn't depend on any single CI provider. Every API response carries -`sep55_attestation` as its own field (present/absent plus attestation -details when present), alongside the rebuild verdict, so explorers can render -both signals and consumers can apply their own policy about which they -require. During the grant we will detect and surface existing SEP-55 -attestations for every contract we index, so the service strengthens the -SEP-55 ecosystem rather than competing with it. + governance process: public Dockerfile and build context, the image itself + reproducibly buildable, digest published by its maintainer, and a public + review window (a PR against the allowlist repo). This keeps the ecosystem + from being bottlenecked on SDF images without diluting `sdf-trusted`. +- **`arbitrary`** — any other digest-pinned image. We still run the rebuild + (the bytes matched or they didn't), but the tier tells consumers the + environment is unvetted. +- **`unknown`** — the digest can no longer be resolved. + +**Allowlist eviction downgrades; it never deletes.** If a digest is removed — +say a vulnerability turns up in an image — past verifications that used it +keep their records. Their trust tier is downgraded, the change is appended to +the record's history with a timestamp and reason, and API responses reflect it +immediately. Trust signals age; evidence does not. + +### SEP-55 and SEP-58: two different questions + +SEP-55 attestations and SEP-58 rebuilds are **complementary trust levels, +exposed as separate API fields** — never collapsed into one badge. SEP-55 +("Contract Build Verification") answers *"did a trusted CI pipeline build +this Wasm from this repo?"* via GitHub artifact attestations: provenance from +a named builder, available instantly, no rebuild cost. A SEP-58 rebuild +answers *"does this source, in this environment, produce exactly these +bytes?"* via independent re-execution. SEP-58's own text calls the two +complementary, "address[ing] the same trust question with different +trade-offs." A contract can and ideally does carry both. We detect and +surface existing SEP-55 attestations for every contract we index, so the +service strengthens that ecosystem rather than competing with it. ## 5. Multi-verifier architecture & decentralization -The RFP is explicit that the ecosystem needs a shared result layer with no -single hardcoded verifier, and the architecture delivers that as a structural -property rather than a promise: - -**Every verifier instance is self-hostable.** The verifier is our open-source -codebase (Apache-2.0); anyone — an explorer, an auditor, a foundation, a -skeptical developer — can run one from the public repository and Docker -images. There is no closed component, no privileged build, and no -functionality reserved for our deployment. - -**Every verifier holds an ed25519 identity key and signs each result.** A -verification result is the canonical encoding of: the on-chain Wasm SHA-256, -the source artifact digest (the content address from §3), the verdict, the -`bldimg` image digest, and the timestamp — signed with the instance's ed25519 -key. Signatures make results portable: a result fetched from our API, relayed -through an explorer's cache, or mirrored by a third party carries its own -proof of which verifier produced it and that it was not altered in transit. - -**The public API serves results from all known verifiers.** Self-hosted -instances can register their public key and result endpoint -(`GET /v1/verifiers` lists them; registration is open and permissionless, -subject only to anti-abuse rate limits), and the aggregation layer ingests and -serves their signed results next to our own. A query for a contract returns -*the set of per-verifier results*, not a single merged opinion. - -**Consumers configure a trusted-verifier set.** An explorer might trust SDF's -instance and ours; a wallet might require two independent `FULL_MATCH` -results; an auditor might trust only their own instance. The client SDK ships -with this policy surface built in. - -**Disagreement is rendered per-verifier, never averaged away.** If verifier X -reproduces the bytes and verifier Y does not, the API reports both, and the -reference UI renders exactly that: - -``` -[√] Verified by X FULL_MATCH image: sdf-trusted -[!] Mismatching verification by Y NO_MATCH image: sdf-trusted -``` - -Disagreement between verifiers using the same image digest and source artifact -is a loud, important signal — it means either non-determinism in the toolchain -or a misbehaving verifier — and hiding it behind a majority vote would discard -precisely the information that matters most. +The RFP asks for a shared result layer with no single hardcoded verifier. +That is a structural property here, not a promise: + +- **Every verifier instance is self-hostable.** The verifier is our + open-source codebase; anyone — an explorer, an auditor, SDF — can run one. + No closed components, nothing reserved for our deployment. +- **Every verifier holds an ed25519 identity key and signs each result**: a + canonical encoding of the Wasm SHA-256, the source artifact digest, the + verdict, the `bldimg` digest, and the timestamp. Signed results are + portable — relayed or cached anywhere, they still prove who produced them + and that they weren't altered. +- **The public API serves results from all known verifiers.** Instances + register their public key and endpoint (open, permissionless, rate-limited); + the aggregation layer serves their signed results next to ours. A query + returns the set of per-verifier results, not one merged opinion. +- **Consumers configure a trusted-verifier set.** An explorer might trust + SDF's instance and ours; a wallet might require two independent + `FULL_MATCH` results. The client SDK ships with this policy surface. +- **Disagreement is rendered per-verifier, never averaged away:** + + ``` + [√] Verified by X FULL_MATCH image: sdf-trusted + [!] Mismatching verification by Y NO_MATCH image: sdf-trusted + ``` + + Two verifiers disagreeing on the same image and source means toolchain + non-determinism or a misbehaving verifier — exactly the signal a majority + vote would hide. **Our hosted deployment is the first instance, not a privileged one.** It -bootstraps the network and gives explorers something to integrate against on -day one, but nothing in the protocol distinguishes it: it signs with its key -like any other instance, appears in `GET /v1/verifiers` like any other -instance, and can be dropped from any consumer's trusted set. The -decentralization goal of the RFP is met by making the hosted service -replaceable from the start. +bootstraps the network and gives explorers something to integrate on day one, +but it signs like any other instance, lists like any other instance, and can +be dropped from any consumer's trusted set. ## 6. Public API specification -The API is free, public, unauthenticated for reads, and versioned under -`/v1`. The four core endpoints: +Free, public, unauthenticated reads, versioned under `/v1`: - **`GET /v1/contract/{id}`** — resolve a contract ID to its current Wasm - hash and return all known verifications of that hash (plus, for upgraded - contracts, pointers to verifications of previously-referenced hashes). -- **`GET /v1/wasm/{hash}`** — all known verifications for a Wasm hash; the - canonical lookup, since verification is keyed by bytecode. -- **`POST /v1/verifications`** — submit a verification request: a target - (contract ID or Wasm hash), the SEP-58 fields (read from `contractmetav0` - when present, supplied in the request body for the retroactive path), and - the source mode. Returns `202` with a job resource that resolves to the - signed result. -- **`GET /v1/verifiers`** — the registry of known verifier instances: ed25519 - public key, operator metadata, endpoint, and status. - -Response schema sketch for a single verification record: + hash and return all known verifications of it (with pointers to previous + hashes for upgraded contracts). +- **`GET /v1/wasm/{hash}`** — all verifications for a Wasm hash; the + canonical lookup. +- **`POST /v1/verifications`** — submit a verification request: target + (contract ID or Wasm hash), SEP-58 fields (read from `contractmetav0` when + present, supplied in the body for the retroactive path), source mode. + Returns `202` with a job resource. +- **`GET /v1/verifiers`** — known verifier instances: ed25519 public key, + operator metadata, endpoint, status. + +A verification record: ```jsonc { @@ -340,13 +256,13 @@ Response schema sketch for a single verification record: "tarball_sha256": null }, "source_mode": "repo", // repo | tarball | content-addressed - "source_artifact": { // what was actually rebuilt (§3) + "source_artifact": { "sha256": "…", "ipfs_cid": "bafy…", // null when retention not permitted (mode 3) "retained": true }, "image_trust_tier": "sdf-trusted", // sdf-trusted | publicly-auditable | arbitrary | unknown - "image_trust_history": [ // populated on downgrade (§4) + "image_trust_history": [ { "tier": "sdf-trusted", "at": "2026-07-01T12:00:00Z", "reason": "allowlisted" } ], "sep55_attestation": { // distinct field; never merged into verdict @@ -363,388 +279,281 @@ Response schema sketch for a single verification record: } ``` -**Versioning policy.** `/v1` is stable from the moment it is documented: -changes within v1 are strictly additive (new fields, new optional -parameters); anything breaking ships as `/v2` with both versions served -through a published deprecation window of at least six months. **We commit -explicitly to conforming to the forthcoming verifier-API SEP once it is -authored**: when that SEP lands, we will implement it as the next API version, -serve it alongside `/v1` during transition, contribute implementation feedback -to the SEP process from our production experience, and ship the client SDK -against the SEP-conformant surface. (The verifier-API SEP does not yet exist -as a numbered document; we treat conformance as a tracked deliverable, not a -dependency that blocks launch.) - -Badge and embed endpoints for explorers (`GET /v1/badge/{contractId}.svg` and -a JSON embed payload) ride on the same data and are described in §12. +**Versioning.** Changes within `/v1` are strictly additive; anything breaking +ships as `/v2`, with both served through a deprecation window of at least six +months. **We commit to conforming to the forthcoming verifier-API SEP once it +is authored**: we will implement it as the next API version, serve it +alongside `/v1` during transition, and feed our production experience back +into the SEP process. (No numbered SEP exists yet; conformance is a tracked +deliverable, not a launch blocker.) + +Badge and embed endpoints for explorers ride on the same data (§12). ## 7. Submission flows -**CLI submission.** The natural developer flow builds on the in-progress -stellar-cli work rather than competing with it. PR +**CLI.** The developer flow builds on the in-progress stellar-cli work +rather than competing with it. [stellar-cli#2585](https://github.com/stellar/stellar-cli/pull/2585) adds -`stellar contract build --verifiable` — a reproducible build inside a -digest-pinned container that stamps the SEP-58 fields into the Wasm — and PR +`stellar contract build --verifiable` (a reproducible build in a digest-pinned +container that stamps the SEP-58 fields into the Wasm) and [#2586](https://github.com/stellar/stellar-cli/pull/2586) adds `stellar -contract verify`, which re-runs the recorded build locally and byte-compares. -Those commands are deliberately local and one-to-one: every verifier pays the -full rebuild cost, and no verdict persists for anyone else. **Our service is -the shared aggregation layer above that CLI capability, not a competitor to -it**: a contract built with `--verifiable` is submittable to -`POST /v1/verifications` with zero additional metadata (the service reads the -SEP-58 fields straight out of `contractmetav0`), the hosted rebuild runs in -the same digest-pinned image the CLI recorded, and the resulting signed -verdict is then queryable by every explorer and wallet — once, for everyone, -instead of once per consumer. `stellar contract verify` remains the -trust-minimized local fallback for anyone who prefers not to trust any -verifier at all, which is exactly the relationship Sourcify-style services -have with local compiler runs on other chains. **We commit to supporting -whichever CLI↔service interaction shape SDF names** — service-mediated -submission (the CLI posts to a verifier and polls) or on-chain result -discovery (the CLI reads verdicts from where the ecosystem publishes them) — -and will implement the corresponding CLI integration PRs as part of milestone -M4. - -**Web submission.** A web UI covers developers not working from the CLI: -paste a contract ID, the service reads `contractmetav0`, pre-fills the SEP-58 -fields, and lets the submitter confirm or supply the source mode inputs. The -same form accepts a tarball upload for modes 2 and 3. - -**Retroactive verification — a stated RFP priority we treat as such.** -Contracts deployed before SEP-58 tooling existed have no embedded metadata, -and non-upgradable contracts can never add it. For these, `POST -/v1/verifications` accepts the full SEP-58 field set **in the request body as -an off-chain metadata submission**: the submitter asserts "this Wasm hash was -built from this source in this image with these options," and the service -tests the assertion by rebuilding. The verdict is exactly as strong as in the -embedded case — the rebuild either reproduces the deployed bytes or it does -not; the embedded fields were never trusted, only tested. The response -records that the claim arrived off-chain (`claim_channel: -"offchain-submission"` vs `"contractmetav0"`) so consumers can distinguish -the provenance of the *claim* while relying identically on the *evidence*. -This is how the large existing population of deployed Soroban contracts — -including every non-upgradable one — becomes verifiable without redeployment. - -**Docs-to-verified in under 15 minutes.** We ship and continuously test a -quickstart walkthrough with an explicit budget: from landing on the docs to a -green `FULL_MATCH` on a testnet contract in under 15 minutes, on the path -`stellar contract build --verifiable` → deploy → submit (one CLI command or -one form) → query. The MVP repo's verification loop (build, deploy, verify -the fixture) already runs in well under that budget locally; the grant work -keeps the hosted path inside it, and the walkthrough is exercised in CI -against the live testnet deployment so regressions in the developer -experience fail a build rather than a first impression. +contract verify` (re-run the recorded build locally and byte-compare). Those +commands are deliberately local and one-to-one: every verifier pays the full +rebuild cost and no verdict persists for anyone else. **Our service is the +shared aggregation layer above that capability, not a competitor**: a +contract built with `--verifiable` is submittable to `POST /v1/verifications` +with zero extra metadata, the hosted rebuild runs in the same recorded image, +and the signed verdict becomes queryable by everyone — once, instead of once +per consumer. `stellar contract verify` remains the trust-nothing local +fallback. **We commit to supporting whichever CLI↔service interaction shape +SDF names** — service-mediated submission or on-chain result discovery — and +will contribute the corresponding CLI integration in milestone M4. + +**Web.** Paste a contract ID; the service reads `contractmetav0`, pre-fills +the SEP-58 fields, and the submitter confirms or supplies the source inputs. +The same form takes a tarball upload for modes 2 and 3. + +**Retroactive verification — a stated RFP priority.** Contracts deployed +before SEP-58 tooling have no embedded metadata, and non-upgradable contracts +never will. For these, `POST /v1/verifications` accepts the full SEP-58 field +set **in the request body as an off-chain metadata submission**. The verdict +is exactly as strong as the embedded case, because embedded fields were never +trusted — only tested: the rebuild either reproduces the deployed bytes or it +doesn't. The record notes the claim channel (`offchain-submission` vs +`contractmetav0`) so consumers can tell where the *claim* came from while +relying identically on the *evidence*. This is how the existing population of +deployed contracts becomes verifiable without redeployment. + +**Docs-to-verified in under 15 minutes.** We ship a quickstart with an +explicit budget — from landing on the docs to a green `FULL_MATCH` on a +testnet contract in under 15 minutes (`build --verifiable` → deploy → submit +→ query) — and run it in CI against the live deployment, so a regression in +the developer experience fails a build, not a first impression. ## 8. Security & threat model -A verification service invites adversarial input by design: its core -operation is "execute a stranger's build." The threat model and mitigations: - -**Malicious build execution.** A Cargo build runs arbitrary code -(`build.rs`, proc-macros). Every rebuild therefore executes in an ephemeral -container, pulled by digest, with **no network access during compilation** — -`--network=none` is already how the MVP's Docker builds run, demonstrated in -`scripts/verify.sh`, not an aspiration. Source acquisition (clone/download) -happens in a separate stage from compilation, so the build itself can never -exfiltrate or fetch. Containers run as an unprivileged user with CPU, memory, -disk, and wall-clock limits; a build that exceeds them is killed and recorded -as `ERROR` with the resource cause. - -**Secret exfiltration.** Build containers carry no secrets to steal: no -service credentials, no signing keys, no cloud metadata access (the metadata -endpoint is blocked at the network layer, which `--network=none` subsumes -during compile). The verifier's ed25519 signing key lives only in the signing -service, which consumes build *outputs* (hashes and verdicts) and never -executes submitted code. - -**Cross-submission contamination.** Each job gets a fresh container and a -fresh workspace; nothing writable is shared between jobs. Dependency caches, -if used for performance, are content-addressed and mounted read-only — a -poisoned artifact cannot enter the cache because entries are keyed by the -digest of what they claim to be. - -**Tarball and artifact integrity.** Downloaded tarballs are hashed and -checked against `tarball_sha256` before extraction; extraction guards against -path traversal and zip-bomb expansion (size and entry-count limits). The -artifact store is append-only and content-addressed (§3), so stored-artifact -tampering is detectable by any reader. - -**Result integrity.** Every result is ed25519-signed over its canonical -encoding (§5); the registry is append-only, and trust-tier changes append -history rather than rewriting records (§4). Compromise of the API serving -layer can therefore deny service but cannot forge verdicts that verify -against the published keys. - -**DoS and abuse on write endpoints.** `POST /v1/verifications` is rate-limited -per source address, deduplicated by (wasm hash, source artifact digest, image -digest) so identical re-submissions return the existing job instead of new -work, capped by global queue depth with honest `429`/queue-position -responses, and bounded per-job by the resource limits above. Read endpoints -are cacheable (results are immutable once signed) and fronted by a CDN, so -query-side load does not touch the build infrastructure at all. - -**Third-party audit before production.** We will undergo a security audit -coordinated by SDF through the **audit bank** before the production/mainnet -launch, scoped to the rebuild sandbox, the signing and registry layer, and -the API; the audit report and resolved findings are a milestone deliverable -(§11), and the threat model above is maintained as a living document in the -repository for the auditors to attack. +The service's core operation is "execute a stranger's build," so the threat +model starts there. + +- **Malicious build code.** Cargo builds run arbitrary code (`build.rs`, + proc-macros). Every rebuild runs in an ephemeral container, pulled by + digest, **with no network during compilation** — `--network=none` is + already how the MVP builds run (`scripts/verify.sh`). Source acquisition + happens in a separate stage, so the build can never fetch or exfiltrate. + Containers run unprivileged with CPU, memory, disk, and time limits; + exceeding them kills the job and records `ERROR` with the cause. +- **Secret exfiltration.** Build containers carry no secrets: no credentials, + no signing keys, no cloud metadata access. The ed25519 key lives only in + the signing service, which consumes hashes and verdicts and never executes + submitted code. +- **Cross-submission contamination.** Fresh container and workspace per job; + nothing writable is shared. Dependency caches, if used, are + content-addressed and mounted read-only, so a poisoned artifact can't enter + them. +- **Tarball and artifact integrity.** Tarballs are hashed against + `tarball_sha256` before extraction; extraction guards against path + traversal and zip bombs. The artifact store is append-only and + content-addressed (§3). +- **Result integrity.** Results are ed25519-signed; the registry is + append-only and tier changes append history (§4). Compromising the API + layer can deny service but cannot forge verdicts that verify against + published keys. +- **DoS and abuse.** `POST /v1/verifications` is rate-limited per source, + deduplicated by (wasm hash, source digest, image digest) so identical + re-submissions return the existing job, and capped by queue depth with + honest `429` responses. Reads are immutable once signed, hence CDN-cached — + query load never touches build infrastructure. +- **Third-party audit.** A security audit coordinated by SDF through the + **audit bank** before production launch, scoped to the sandbox, the + signing/registry layer, and the API. The report and resolved findings are a + milestone deliverable (§11); this threat model is a living document in the + repo for auditors to attack. ## 9. Operations & sustainability -**Availability.** The query API targets **99%+ uptime**, achieved by -separating the read path from the build path: reads are served from a -replicated database behind stateless API instances and a CDN (signed results -are immutable, hence aggressively cacheable), so build-side incidents cannot -take down queries. The write path degrades gracefully — if workers are down, -submissions queue and report status honestly. +**Availability.** The query API targets **99%+ uptime** by separating read +and build paths: reads come from a replicated database behind stateless API +instances and a CDN, so build incidents can't take down queries. If workers +are down, submissions queue and report status honestly. **Latency.** Verification of standard-size contracts **completes within 5 -minutes, or the API returns an explicit queued status** — `POST -/v1/verifications` answers `202` immediately with a job resource exposing -state (`queued` → `building` → `done`), queue position, and a retry hint, so -integrators never hang on a long poll. The MVP fixture rebuilds in well under -a minute; the five-minute budget covers realistic dependency-heavy contracts, -and per-toolchain warm image pulls plus read-only dependency caches (§8) keep -the common case far below it. - -**Monitoring, runbook, on-call.** The deployment ships with metrics -(API availability and latency percentiles, queue depth, build duration -percentiles, verdict-rate anomalies — a spike in `NO_MATCH` across many -contracts is an early non-determinism alarm), alerting on SLO burn, a public -status page, and an operational runbook covering the failure modes we can -enumerate (RPC outages, image-registry outages, queue backpressure, allowlist -rollback). Bleu staffs an on-call rotation for the production service through -the grant period and the post-grant tail. - -**Retention and egress.** Verification records are retained indefinitely — -they are small, append-only, and their value compounds. Source artifacts are -retained for the life of the service and pinned to IPFS where licensing -permits, with the IPFS CID in the record so the ecosystem can co-pin; mode-3 -artifacts are retained only with submitter consent (§3). **Egress costs for -the public API and artifact downloads are owned by Bleu** within the grant -and tail period, bounded by CDN caching and by serving large artifacts -preferentially via IPFS; the runbook documents the cost model so any future -operator inherits a known bill, not a surprise. - -**Post-grant ownership and funding tail.** Bleu commits to operating the -hosted service for at least **12 months beyond the final grant milestone** at -our own cost, while working toward the service's long-term home: because the -codebase is self-hostable and results are portable signed statements, the -hosted instance is replaceable by design (§5), and we will actively support -ecosystem operators (explorers, SDF, community) standing up peer instances — -the healthiest end state is one where our instance is one of several. Ongoing -maintenance economics (toolchain image updates, SEP conformance work) are -deliberately small: the heavy costs are bounded by caching and IPFS, and we -will propose follow-on community funding only if peer adoption has not -materialized by the end of the tail. +minutes or returns an explicit queued status**: submissions answer `202` +immediately with a job resource (`queued` → `building` → `done`, queue +position, retry hint). The MVP fixture rebuilds in well under a minute; warm +image pulls and read-only dependency caches keep the common case far below +the budget. + +**Monitoring, runbook, on-call.** Metrics on API availability and latency, +queue depth, build duration, and verdict-rate anomalies (a `NO_MATCH` spike +across many contracts is an early non-determinism alarm); alerting on SLO +burn; a public status page; an operational runbook covering RPC outages, +registry outages, backpressure, and allowlist rollback. Bleu staffs on-call +through the grant and the tail period. + +**Retention and egress.** Verification records are kept indefinitely — small, +append-only, compounding in value. Source artifacts are kept for the life of +the service per §3's storage rules, with CIDs published so the ecosystem can +co-pin. **Egress for the public API and artifact downloads is owned by Bleu** +through the grant and tail, bounded by CDN caching and IPFS; the runbook +documents the cost model so a future operator inherits a known bill. + +**Post-grant ownership.** Bleu operates the hosted service for at least **12 +months after the final milestone** at our own cost, while actively helping +ecosystem operators stand up peer instances — the healthiest end state is one +where ours is one of several (§5). Ongoing maintenance is deliberately small +(image updates, SEP conformance); we will propose follow-on community funding +only if peer adoption hasn't materialized by the end of the tail. ## 10. Prior art & approach justification -We reviewed the relevant public repositories rather than reasoning from -memory; the observations below are from the projects' current public code and -documentation, and they jointly justify a *build* (with heavy pattern reuse) -rather than an *adapt* of any single codebase. +We reviewed the public repositories below before writing this; the +observations are from their current code and docs. Together they justify +building on our Soroban-native MVP while importing proven patterns, rather +than adapting any single codebase. **Sourcify ([github.com/ethereum/sourcify](https://github.com/ethereum/sourcify)).** -Sourcify is the proof that an open, shared verification layer works at -ecosystem scale, and we adopt several of its patterns directly: the open-data -repository of verified contracts; the async job API (`POST -/v2/verify/{chainId}/{address}` returning a `verificationId` to poll, then -`GET /v2/contract/{chainId}/{address}` for results — the same -submit/poll/lookup shape as our §6); a monitor service that watches chains -and proactively verifies new deployments; and the graded verdict vocabulary -("exact match" vs "match", formerly full/partial). But Sourcify's matching -mechanism cannot be adapted to Soroban, because it rests on two EVM-specific -facts: solc embeds a CBOR-encoded metadata hash *inside the deployed -bytecode* that transitively commits to the source files (Sourcify's own docs: -"Change a byte in the source code → Source code hash changes → Metadata -changes → Metadata hash changes → Deployed bytecode changes"), and -compilation is a pure function of a stdJSON input plus a single versioned -compiler binary — so Sourcify never needs a pinned *environment*, just a -pinned solc. Soroban Wasm has no compiler-enforced source commitment -(SEP-46 `contractmetav0` fields are self-declared), and a cargo build's -output depends on the whole toolchain environment. Hence our approach: -environment-pinned full rebuilds in digest-addressed images, with the -`METADATA_ONLY_MATCH` verdict as the Soroban analogue of Sourcify's partial -match (the one part of the gradient that does transfer, since `contractmetav0` -is a strippable custom section — already implemented in the MVP). - -**solana-verifiable-build / solana-verify +Proof that an open, shared verification layer works at ecosystem scale. We +adopt its open-data repository of verified contracts, its async job API +(`POST /v2/verify/{chainId}/{address}` → poll a `verificationId` → `GET +/v2/contract/...`), its proactive monitor service, and its graded verdict +vocabulary ("exact match" vs "match"). But its matching mechanism can't be +adapted: solc embeds a CBOR metadata hash *inside the deployed bytecode* that +commits to the source files ("Change a byte in the source code → … → Deployed +bytecode changes," per its docs), and compilation is a pure function of a +stdJSON input plus one versioned compiler binary — Sourcify never needs a +pinned *environment*, just a pinned solc. Soroban Wasm has no +compiler-enforced source commitment (`contractmetav0` is self-declared), and +a cargo build depends on the whole toolchain. Hence our approach: +environment-pinned rebuilds in digest-addressed images, with +`METADATA_ONLY_MATCH` as the one part of Sourcify's gradient that transfers +(already implemented in the MVP). + +**solana-verifiable-build ([github.com/Ellipsis-Labs/solana-verifiable-build](https://github.com/Ellipsis-Labs/solana-verifiable-build)).** -The closest architectural template, and validation that digest-pinned Docker -rebuilds are the right primitive for toolchain-sensitive targets: build -images are selected by pinned digest, installer scripts in generated -Dockerfiles are checksum-pinned, and the README is explicit that verified -builds "should not be considered a complete security solution" — -source↔binary correspondence, not code safety, which is our framing too. Its -trust split is instructive: build parameters (repo, commit, args) are -published on-chain in a PDA signed by the program's *upgrade authority* (the -claim channel), while a remote verifier — OtterSec's API at `verify.osec.io`, -reached via `solana-verify remote submit-job` — re-runs the build and -publishes the verdict explorers consume (the evidence channel), with local -`verify-from-repo` always available as the trust-nothing fallback. We keep -that claim/evidence split but improve on two known weaknesses: Stellar's -claim channel is SEP-58 metadata embedded in the Wasm itself (no separate -on-chain registry program to deploy and trust), and where Solana's ecosystem -effectively trusts a single hosted verifier, our §5 architecture makes -multiple signing verifiers and per-verifier disagreement rendering the -day-one design rather than a retrofit. - -**Stellar Expert's build workflow and the SDF prototype landscape.** The RFP -context names an SDF experimental prototype; in our review we found no public -repository named `stellar-experimental/contract-verifications` (the obvious -URLs 404), and the substantive existing Soroban prior art is +The closest architectural template: build images selected by pinned digest, +checksum-pinned installers, and a README explicit that verified builds +"should not be considered a complete security solution" — source↔binary +correspondence, not code safety, which is our framing too. Its trust split is +instructive: build parameters live on-chain in a PDA signed by the program's +upgrade authority (the claim), while OtterSec's hosted API at +`verify.osec.io` re-runs the build and publishes the verdict explorers +consume (the evidence), with local `verify-from-repo` as the trust-nothing +fallback. We keep the claim/evidence split and fix two weaknesses: Stellar's +claim channel is SEP-58 metadata in the Wasm itself (no separate registry +program to trust), and where Solana effectively trusts one hosted verifier, +multiple signing verifiers with per-verifier disagreement rendering are our +day-one design (§5). + +**Stellar Expert's build workflow.** The RFP context names an SDF +experimental prototype; we found no public repo named +`stellar-experimental/contract-verifications` (the obvious URLs 404). The +substantive existing Soroban prior art is **[stellar-expert/soroban-build-workflow](https://github.com/stellar-expert/soroban-build-workflow)** -(OrbitLens), the reusable GitHub Actions workflow behind stellar.expert's -contract validation and the origin of what became SEP-55. It compiles -contracts in CI, publishes releases with the Wasm and its SHA-256, and -"generates and uploads build attestations" (GitHub artifact attestations); -validation checks the attestation's `runDetails.builder.id` against the -trusted workflow path. Three observations from its README and the SEP-55 -discussion ([stellar discussion -#1573](https://github.com/orgs/stellar/discussions/1573)) shaped this -proposal. First, the trust chain is contract → GitHub attestation → GitHub -Actions runner: nobody independently rebuilds, and discussion participants -noted attestations don't prevent build-time injection. Second, it is -single-surface: StellarExpert is effectively the lone consumer/validator, and -GitHub is a single point of failure (Rekor transparency logs, IPFS, and -GitLab support were raised in the discussion but not implemented). Third — -and most telling — the README itself warns that contracts must be deployed -*directly from the workflow's release artifacts* "otherwise the deployed -contract hash may not match the release artifacts due to compilation -environment variations." That is the workflow acknowledging exactly the gap a -rebuild service fills: Soroban builds are environment-sensitive, so a -verification layer must pin the environment and re-execute, not attest. None -of this makes SEP-55 inferior — it answers the provenance question cheaply -and instantly, and we surface its attestations as a first-class field (§4) — -but it is why the rebuild layer needs to exist as well. +(OrbitLens) — the GitHub Actions workflow behind stellar.expert's contract +validation and the origin of SEP-55. It compiles contracts in CI, publishes +releases with the Wasm and its SHA-256, and uploads GitHub artifact +attestations; validation checks the attestation's builder ID against the +trusted workflow path. Three observations shaped this proposal. The trust +chain is contract → GitHub attestation → GitHub Actions runner — nobody +independently rebuilds, and the SEP discussion +([#1573](https://github.com/orgs/stellar/discussions/1573)) notes +attestations don't prevent build-time injection. It is single-surface: +stellar.expert is effectively the lone validator, and GitHub a single point +of failure. And its own README warns contracts must be deployed directly from +the workflow's release artifacts, "otherwise the deployed contract hash may +not match … due to compilation environment variations" — the workflow itself +acknowledging that Soroban builds are environment-sensitive, which is exactly +the gap a rebuild service fills. None of this makes SEP-55 inferior; it +answers the provenance question cheaply and instantly, and we surface it as a +first-class field (§4). **stellar-cli PRs [#2585](https://github.com/stellar/stellar-cli/pull/2585) -and [#2586](https://github.com/stellar/stellar-cli/pull/2586)** (both open -and unmerged at the time of writing) supply the local half of the SEP-58 -story: `build --verifiable` rejects tag-only image references (digest-pinned -`--image` required), implies `--locked`, requires a clean git tree, and -stamps `bldimg`, `bldopt`, and source identification into the Wasm; `contract -verify` reads the embedded metadata, re-runs the recorded build, and -byte-compares, with a trust prompt before pulling unrecognized images and -tarball sources "never default-trusted." What the CLI pair deliberately does -not provide — persistent verdicts, a queryable registry, badges, bulk and -retroactive verification, multi-verifier aggregation — is precisely this -service (§7). +and [#2586](https://github.com/stellar/stellar-cli/pull/2586)** (both open at +the time of writing) supply the local half of the SEP-58 story, as described +in §7. What they deliberately don't provide — persistent verdicts, a +queryable registry, badges, retroactive and bulk verification, multi-verifier +aggregation — is this service. **Why build rather than adapt:** Sourcify's core is EVM-metadata-specific; -solana-verify's claim channel is a Solana program and its service half -(OtterSec's backend) is not the open-source part; the SEP-55 workflow is an -attestation producer, not a rebuild verifier. Meanwhile the genuinely -chain-specific pieces we need — RPC chain reading, `contractmetav0` parsing, +solana-verify's service half (OtterSec's backend) is not the open-source +part; the SEP-55 workflow produces attestations, it doesn't rebuild. The +chain-specific pieces we need — RPC reading, `contractmetav0` parsing, verdict logic, the pinned rebuild container — are already written and tested -in our MVP repo. We therefore build the service on our existing Soroban-native -foundation while deliberately importing the proven patterns: Sourcify's open -data and API shape, solana-verify's digest-pinned rebuild discipline and -claim/evidence split, and SEP-55 coexistence as designed by SEP-58 itself. +in our repo. ## 11. Milestones -Milestones are mapped directly to the RFP's deliverables list. Each milestone -has an objective completion test. +Mapped to the RFP deliverables list; each has an objective completion test. **M1 — Audit-ready service codebase.** The open-source, self-hostable -codebase (RFP deliverable 1) feature-complete for audit: multi-verifier -signing (§5), allowlist enforcement with trust tiers and downgrade semantics -(§4), all three source modes with IPFS retrieval and the tamper-evident -artifact store (§3), sandboxed rebuild workers (§8), and the written threat -model. *Done when:* a third party can clone the repo, stand up a full -verifier instance from docs alone, and verify the MVP fixture end to end; -audit scope agreed with the SDF audit bank. - -**M2 — Security audit and hosted deployment.** Third-party audit through the -SDF audit bank with the report published and findings resolved (RFP -deliverable: audit report and resolved findings); the hosted service deployed -publicly against **testnet and mainnet** (RFP deliverable: public -deployment), including the mainnet chain-reader configuration and the -retroactive submission path. *Done when:* the audit report and remediations -are public, and `GET /v1/contract/{id}` answers for both networks on the -production endpoint. - -**M3 — Stable public API, client SDK, and documentation.** The `/v1` API -frozen and documented with OpenAPI (RFP deliverable: stable documented API); -the client SDK/reference client published, with the explicit conformance -commitment to the forthcoming verifier-API SEP (RFP deliverable: SDK/client -conforming to the verifier-API SEP) — implemented against the SEP if it has -been authored by this milestone, otherwise against `/v1` with the SEP -migration tracked as a standing commitment (§6); the image allowlist policy -and governance process published (RFP deliverable: allowlist policy -documentation). *Done when:* an integrator can go from the docs to rendering -verification state without contacting us, and the under-15-minute walkthrough -passes in CI against the live deployment. - -**M4 — Integrations.** The reference integrations of §12 shipped (badge -endpoint, explorer embed, SDK example) plus integration documentation (RFP -deliverable: integration docs) and at least one reference integration landed -with Stellar Lab or a cooperating verifier/explorer (RFP deliverable: -reference integration), alongside the CLI interaction work in whichever shape -SDF names (§7). *Done when:* a named partner surface renders our verification -results in production for testnet and mainnet contracts. - -**M5 — Production handoff and sustainability.** The operational runbook -published (RFP deliverable: operational runbook); monitoring, status page, -and on-call in steady state (§9); retention/egress cost model documented; the -12-month post-grant operating tail formally begun, including the peer-operator -support program (§9). *Done when:* SDF accepts the runbook, the SLO dashboards -are public, and at least one external party has a peer verifier instance -running or in progress. +codebase feature-complete for audit: result signing (§5), allowlist +enforcement with trust tiers and downgrade semantics (§4), all three source +modes with IPFS and the artifact store (§3), sandboxed workers (§8), written +threat model. *Done when:* a third party can stand up a full verifier from +docs alone and verify the MVP fixture end to end; audit scope agreed with the +SDF audit bank. + +**M2 — Security audit and hosted deployment.** Audit through the SDF audit +bank, report published, findings resolved; the hosted service live on +**testnet and mainnet**, including the retroactive submission path. *Done +when:* the audit report and remediations are public and +`GET /v1/contract/{id}` answers for both networks in production. + +**M3 — Stable API, SDK, and docs.** `/v1` frozen and documented with OpenAPI; +the client SDK published, conforming to the verifier-API SEP if authored by +then, otherwise to `/v1` with SEP migration as a standing commitment (§6); +the allowlist policy and governance process published. *Done when:* an +integrator can go from docs to rendering verification state without +contacting us, and the under-15-minute walkthrough passes in CI against the +live deployment. + +**M4 — Integrations.** The badge endpoint, explorer embed, and SDK example +(§12); integration docs; the CLI interaction in whichever shape SDF names +(§7); at least one reference integration landed with Stellar Lab or a +cooperating explorer/verifier. *Done when:* a named partner surface renders +our results in production for both networks. + +**M5 — Production handoff.** Operational runbook published; monitoring, +status page, and on-call in steady state; retention/egress cost model +documented; the 12-month post-grant tail and peer-operator support program +begun (§9). *Done when:* SDF accepts the runbook, SLO dashboards are public, +and at least one external party has a peer verifier running or in progress. ## 12. Integrations plan The service is only useful where users already look, so reference -integrations are deliverables, not aspirations: - -- **Badge endpoint** — `GET /v1/badge/{contractId}.svg`: a cacheable SVG - rendering the verdict and image trust tier, designed for READMEs and - explorer pages, with per-verifier variants reflecting §5's disagreement - rendering. -- **Explorer embed snippet** — a documented JSON payload plus a small, - dependency-free web component an explorer can drop in to render the full - per-verifier result set (verdicts, trust tiers, SEP-55 presence, source - links) from one API call. -- **Client SDK example** — a TypeScript client (the natural extension of the - MVP's reader package) wrapping the four endpoints, with the trusted-verifier - policy surface built in and a worked example that resolves a contract ID to - a render-ready verification summary. - -During the grant we will engage the partners the RFP context names — -**OrbitLens / Stellar Expert** (whose build workflow pioneered Soroban -verification UX and whose explorer is its most prominent surface), **Aha Labs -/ rgstry.xyz**, **57B**, and **Stellar Lab** (whose Contract Explorer -currently displays SEP-55 attestation state and is the natural first surface -for showing the rebuild verdict alongside it) — to land the M4 reference -integration and to make sure the embed and SDK fit how their products -actually consume data, rather than how we imagine they do. Stellar Expert -specifically is a natural early *peer verifier* candidate, not just a -consumer, given OrbitLens's role in originating SEP-55. +integrations are deliverables: + +- **Badge endpoint** — `GET /v1/badge/{contractId}.svg`: a cacheable SVG with + verdict and trust tier, for READMEs and explorer pages, with per-verifier + variants matching §5's disagreement rendering. +- **Explorer embed** — a documented JSON payload plus a small dependency-free + web component that renders the full per-verifier result set from one API + call. +- **Client SDK example** — a TypeScript client (extending the MVP's reader + package) wrapping the four endpoints, with the trusted-verifier policy + built in and a worked contract-ID-to-summary example. + +During the grant we will engage the partners the RFP names — **OrbitLens / +Stellar Expert**, **Aha Labs / rgstry.xyz**, **57B**, and **Stellar Lab** +(whose Contract Explorer already shows SEP-55 attestation state and is the +natural first surface for the rebuild verdict beside it) — to land the M4 +reference integration and to fit the embed and SDK to how their products +actually consume data. Stellar Expert, given OrbitLens's role in originating +SEP-55, is also a natural early *peer verifier*, not just a consumer. ## 13. Compliance & openness -The service is operated as a public good, and the RFP's compliance -requirements are properties of the design rather than policies bolted on: - -- **No KYC, no gated access.** Read endpoints are anonymous and free; - submission requires no account — anti-abuse is handled by rate limits and - resource caps (§8), not identity. Nothing about a submitter is collected - beyond what operating the service requires (transient rate-limit state). -- **Apache-2.0, everything.** The verifier, API, workers, SDK, badge - rendering, deployment configuration, and documentation are all in the - public repository under Apache-2.0 — the license the MVP repo already - carries. -- **Self-hostable by construction.** A single documented deployment brings up - a complete verifier instance; M1's completion test is literally that a third - party can do this from docs alone. No closed dependencies, no privileged - signing authority, no calls home. -- **Community-operable over time.** The multi-verifier design (§5) means the - ecosystem can outgrow us without migration: results are portable signed - statements, peer instances are first-class from day one, and the post-grant - plan (§9) actively works toward a steady state where the hosted instance we - run is one among several. The healthiest possible outcome of this grant is - an ecosystem that no longer depends on any single operator — including us. - +- **No KYC, no gated access.** Reads are anonymous and free; submission needs + no account — abuse is handled by rate limits and resource caps (§8), not + identity. +- **Apache-2.0, everything.** Verifier, API, workers, SDK, badges, deployment + config, docs — all public under the license the MVP repo already carries. +- **Self-hostable by construction.** One documented deployment brings up a + complete verifier; M1's completion test is that a third party can do it + from docs alone. No closed dependencies, no privileged signer, no calls + home. +- **Community-operable over time.** Results are portable signed statements + and peer instances are first-class (§5), so the ecosystem can outgrow us + without migration. The best outcome of this grant is an ecosystem that no + longer depends on any single operator — including us. From db716d29dc0197e703ed7f351c39a8fed64e891f Mon Sep 17 00:00:00 2001 From: Pedro Yves Fracari Date: Fri, 12 Jun 2026 17:15:47 -0300 Subject: [PATCH 4/4] Replace proposal doc with neutral architecture & roadmap doc docs/PROPOSAL.md becomes docs/ARCHITECTURE.md: same technical content (SEP-58 pipeline mapping, source modes, trust tiers, multi-verifier signing, /v1 API, threat model, prior art) reframed as the project's own design doc. All proposal/grant/funding framing removed; milestones become roadmap phases. README, toolchain manifest, and reader comments updated to point at the new doc. --- README.md | 51 +++--- docker/toolchain-manifest.json | 2 +- docs/{PROPOSAL.md => ARCHITECTURE.md} | 229 +++++++++++++------------- reader/src/networks.ts | 2 +- reader/src/verify.ts | 4 +- 5 files changed, 142 insertions(+), 146 deletions(-) rename docs/{PROPOSAL.md => ARCHITECTURE.md} (76%) diff --git a/README.md b/README.md index 8a024ad..18708ec 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,11 @@ is byte-for-byte the published source — by independently **re-compiling** the source on neutral infrastructure and comparing hashes, not by trusting a build provenance attestation. -This repo is the working MVP behind our grant proposal — -**[docs/PROPOSAL.md](docs/PROPOSAL.md)** — responding to the SDF RFP "Contract -Source Verification Service": a single pinned toolchain, a chain reader, and a -verify-by-contract-ID CLI. The hosted multi-verifier service, `/v1` public -API, UI, multi-toolchain selection, and explorer integrations are grant scope -(see [Roadmap](#roadmap)). +This repo is the working MVP of the service: a single pinned toolchain, a +chain reader, and a verify-by-contract-ID CLI. The full design — the hosted +multi-verifier service, `/v1` public API, UI, multi-toolchain selection, and +explorer integrations — is specified in +**[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** (see [Roadmap](#roadmap)). License: **Apache-2.0**. @@ -54,9 +53,9 @@ The diagram uses the SDK method names as they actually exist on ```mermaid flowchart TD - Dev[Developer / Auditor] -->|contract ID or wasm hash + source repo| UI[Verification UI - React/Next - grant scope] - UI -->|POST /v1/verifications - grant scope| API[Public API - /v1 - grant scope] - API -->|enqueue job| Q[Build Queue - grant scope] + Dev[Developer / Auditor] -->|contract ID or wasm hash + source repo| UI[Verification UI - React/Next - roadmap] + UI -->|POST /v1/verifications - roadmap| API[Public API - /v1 - roadmap] + API -->|enqueue job| Q[Build Queue - roadmap] API -->|fetch on-chain wasm| Reader[Chain Reader - stellar-sdk RPC - MVP] Reader -->|getContractWasmByContractId / getContractWasmByHash| RPC[(Stellar RPC - ContractCodeEntry - SHA-256 hash)] Reader -->|SEP-46 contractmetav0 - SEP-58 bldimg bldopt source_repo source_rev| API @@ -65,17 +64,17 @@ flowchart TD Worker -->|clone repo@commit or local source| Git[(Source - repo / tarball / content-addressed)] Worker -->|stellar contract build --locked - target wasm32v1-none| WASM[Rebuilt WASM + SHA-256 - MVP] WASM -->|compare hash and section diff| Match{Match verdict - MVP} - Match -->|full / metadata-only / none| DB[(Verification registry - ed25519-signed results - grant scope)] - DB -->|mirror artifacts| IPFS[(IPFS pin - grant scope)] - DB --> Badge[Badge endpoint - GET /v1/badge/id.svg - grant scope] - Badge --> Explorer[Explorers - Stellar Expert / Stellar Lab Contract Explorer - grant scope] - DB --> Query[GET /v1/wasm/hash - GET /v1/contract/id - grant scope] + Match -->|full / metadata-only / none| DB[(Verification registry - ed25519-signed results - roadmap)] + DB -->|mirror artifacts| IPFS[(IPFS pin - roadmap)] + DB --> Badge[Badge endpoint - GET /v1/badge/id.svg - roadmap] + Badge --> Explorer[Explorers - Stellar Expert / Stellar Lab Contract Explorer - roadmap] + DB --> Query[GET /v1/wasm/hash - GET /v1/contract/id - roadmap] Query --> UI ``` Pieces labeled **MVP** are implemented and tested in this repo. Pieces labeled -**grant scope** are specified in [docs/PROPOSAL.md](docs/PROPOSAL.md) but not -built here. +**roadmap** are specified in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) but +not built here. ## Stack (plain English) @@ -206,17 +205,17 @@ test/integration.testnet.test.ts`. ## Roadmap -Grant milestones are specified in [docs/PROPOSAL.md §11](docs/PROPOSAL.md); -in summary: +The full roadmap is in [docs/ARCHITECTURE.md §11](docs/ARCHITECTURE.md); in +summary: -| Milestone | Scope | -|-----------|-------| +| Phase | Scope | +|-------|-------| | **MVP (this repo)** | Single pinned toolchain, chain reader, verify-by-ID CLI, deterministic hash-match proof on testnet. | -| M1 — Audit-ready codebase | Self-hostable verifier with ed25519 result signing, image allowlist + trust tiers, all three SEP-58 source modes, IPFS retrieval, sandboxed rebuild workers. | -| M2 — Audit + hosted deployment | Security audit via the SDF audit bank; public **testnet + mainnet** deployment; retroactive (off-chain metadata) submission path. | -| M3 — Stable `/v1` API + SDK + docs | `GET /v1/contract/{id}`, `GET /v1/wasm/{hash}`, `POST /v1/verifications`, `GET /v1/verifiers`; client SDK; allowlist policy doc; under-15-minute walkthrough. | -| M4 — Integrations | Badge endpoint (`GET /v1/badge/{id}.svg`), explorer embed, stellar-cli interaction, partner reference integration. | -| M5 — Production handoff | Runbook, monitoring, on-call, 12-month post-grant operating tail. | +| 1 — Self-hostable verifier core | ed25519 result signing, image allowlist + trust tiers, all three SEP-58 source modes, IPFS retrieval, sandboxed rebuild workers. | +| 2 — Audit + hosted deployment | Independent security audit; public **testnet + mainnet** deployment; retroactive (off-chain metadata) submission path. | +| 3 — Stable `/v1` API + SDK + docs | `GET /v1/contract/{id}`, `GET /v1/wasm/{hash}`, `POST /v1/verifications`, `GET /v1/verifiers`; client SDK; allowlist policy doc; under-15-minute walkthrough. | +| 4 — Integrations | Badge endpoint (`GET /v1/badge/{id}.svg`), explorer embed, stellar-cli interaction, partner reference integration. | +| 5 — Production operations | Runbook, monitoring, on-call, peer-operator support. | ## References @@ -229,7 +228,7 @@ in summary: - [SEP-0058] Contract Build Reproducibility for Verification (`bldimg`, `bldopt`, `source_repo`, `source_rev`, `tarball_url`, `tarball_sha256`) - [Sourcify] — Ethereum source verification (full vs partial match) prior art - OpenZeppelin `stellar-contracts` (Rust Soroban library) -- SDF RFP "Contract Source Verification Service" (Q2 2026) — our response: [docs/PROPOSAL.md](docs/PROPOSAL.md) +- Service design & roadmap: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) [SEP-0046]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0046.md [SEP-0055]: https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0055.md diff --git a/docker/toolchain-manifest.json b/docker/toolchain-manifest.json index 3a00f08..3c888e0 100644 --- a/docker/toolchain-manifest.json +++ b/docker/toolchain-manifest.json @@ -1,5 +1,5 @@ { - "$comment": "Pinned toolchain manifest for the MVP single-toolchain reproducible-build image. The on-chain WASM hash is reproducible only against THIS exact toolchain. In the grant scope (docs/PROPOSAL.md), image selection is driven by the SEP-58 bldimg digest recorded in contractmetav0 or submitted off-chain.", + "$comment": "Pinned toolchain manifest for the MVP single-toolchain reproducible-build image. The on-chain WASM hash is reproducible only against THIS exact toolchain. In the full service (docs/ARCHITECTURE.md), image selection is driven by the SEP-58 bldimg digest recorded in contractmetav0 or submitted off-chain.", "image": "soroscan-verify-builder:rust-1.91.1-cli-26.1.0", "baseImage": "rust:1.91.1-bookworm", "baseImageDigest": "TODO: pin via `docker buildx imagetools inspect rust:1.91.1-bookworm` and record the sha256 here", diff --git a/docs/PROPOSAL.md b/docs/ARCHITECTURE.md similarity index 76% rename from docs/PROPOSAL.md rename to docs/ARCHITECTURE.md index 39e8039..863a2b3 100644 --- a/docs/PROPOSAL.md +++ b/docs/ARCHITECTURE.md @@ -1,30 +1,30 @@ -# Soroscan Verify — Contract Source Verification Service +# Soroscan Verify — Architecture & Roadmap -**Grant proposal in response to the SDF RFP "Contract Source Verification Service" (Q2 2026)** - -- **Applicant:** Bleu (bleu.studio) -- **Repository:** `scf-contract-source-verification` (Apache-2.0, public) -- **Working MVP:** chain reader, verify-by-contract-ID CLI, pinned Docker build toolchain, live testnet fixture with a matching on-chain hash -- **Date:** June 2026 +Soroscan Verify is an open-source (Apache-2.0) source verification service for +Soroban contracts: it proves that a deployed contract's on-chain Wasm is +byte-for-byte the published source by independently rebuilding that source and +comparing hashes. This document describes how the service works, the trust +model behind it, and the roadmap from the current MVP to a hosted public +service. --- -## 1. Summary & current state +## 1. What this is & current state Soroban contracts are deployed as opaque Wasm blobs, stored on the ledger under the SHA-256 of the bytecode. Source verification is therefore one testable question: *can this source be rebuilt into a Wasm whose SHA-256 equals the hash on the ledger?* -We propose a hosted, free, public service that answers that question for the -whole ecosystem. It reads the SEP-58 metadata fields (`bldimg`, `bldopt`, -`source_repo`, `source_rev`, `tarball_url`, `tarball_sha256`), rebuilds the -source inside SDF-allowlisted build images, byte-compares the result against -the deployed Wasm, and serves signed results to explorers, wallets, and the -Stellar CLI through a versioned API. One shared result layer — no per-consumer -rebuilds, no single hardcoded verifier. +Soroscan Verify answers that question for the whole ecosystem. It reads the +SEP-58 metadata fields (`bldimg`, `bldopt`, `source_repo`, `source_rev`, +`tarball_url`, `tarball_sha256`), rebuilds the source inside allowlisted +build images, byte-compares the result against the deployed Wasm, and serves +signed results to explorers, wallets, and the Stellar CLI through a free, +public, versioned API. One shared result layer — no per-consumer rebuilds, no +single hardcoded verifier. -The core of this is already built and public. Our MVP repo proves: +The core is already built and tested in this repo: - **A deterministic rebuild matches the chain.** A sample contract (soroban-sdk 25.3.1, target `wasm32v1-none`) is live on testnet as @@ -38,8 +38,11 @@ The core of this is already built and public. Our MVP repo proves: - **A chain reader over Stellar RPC** (`reader/src/chain-reader.ts`): fetches on-chain Wasm by contract ID or directly by Wasm hash — one verified hash covers every contract instance that shares the bytecode. +- **A SEP-58 metadata reader** (`reader/src/contractmeta.ts`, + `reader/src/sep58.ts`): decodes the SEP-46 `contractmetav0` section, + extracts the six SEP-58 fields, and infers the source mode. - **A verify CLI with a graded verdict model**: `FULL_MATCH` - (byte-identical), `METADATA_ONLY_MATCH` (differs only in the SEP-46 + (byte-identical), `METADATA_ONLY_MATCH` (differs only in the `contractmetav0` custom section, detected by stripping that section and re-comparing), `NO_MATCH`, or `ERROR`. Exit code 0 only on `FULL_MATCH`, so it works in CI. @@ -48,7 +51,7 @@ The core of this is already built and public. Our MVP repo proves: `--network=none`. The resolved toolchain is recorded in `docker/toolchain-manifest.json`. -The grant funds the path from this primitive to the full service: signed +The roadmap (§11) takes this primitive to the full service: signed multi-verifier results, all three SEP-58 source modes, the image allowlist with trust tiers, the public API, retroactive verification, integrations, a third-party audit, and production operations. @@ -96,10 +99,10 @@ flowchart TD DB --> API ``` -The chain reader, verdict logic, and pinned rebuild container are running MVP -code; the queue, registry, signing layer, and API are the grant work. The -service runs against **both mainnet and testnet** from launch (the MVP's -testnet-only guard is lifted in milestone M2). +The chain reader, SEP-58 metadata reader, verdict logic, and pinned rebuild +container are running MVP code; the queue, registry, signing layer, and API +are roadmap work. The service runs against **both mainnet and testnet** (the +MVP's testnet-only guard is lifted in roadmap phase 2). Verification is keyed by **Wasm hash**, not contract ID. The contract-ID endpoint resolves the instance's current code hash and joins to verifications @@ -188,12 +191,12 @@ trade-offs." A contract can and ideally does carry both. We detect and surface existing SEP-55 attestations for every contract we index, so the service strengthens that ecosystem rather than competing with it. -## 5. Multi-verifier architecture & decentralization +## 5. Multi-verifier design & decentralization -The RFP asks for a shared result layer with no single hardcoded verifier. -That is a structural property here, not a promise: +The service is designed as a shared result layer with no single hardcoded +verifier. That is a structural property, not a policy: -- **Every verifier instance is self-hostable.** The verifier is our +- **Every verifier instance is self-hostable.** The verifier is this open-source codebase; anyone — an explorer, an auditor, SDF — can run one. No closed components, nothing reserved for our deployment. - **Every verifier holds an ed25519 identity key and signs each result**: a @@ -224,7 +227,7 @@ bootstraps the network and gives explorers something to integrate on day one, but it signs like any other instance, lists like any other instance, and can be dropped from any consumer's trusted set. -## 6. Public API specification +## 6. Public API Free, public, unauthenticated reads, versioned under `/v1`: @@ -285,7 +288,7 @@ months. **We commit to conforming to the forthcoming verifier-API SEP once it is authored**: we will implement it as the next API version, serve it alongside `/v1` during transition, and feed our production experience back into the SEP process. (No numbered SEP exists yet; conformance is a tracked -deliverable, not a launch blocker.) +roadmap item, not a launch blocker.) Badge and embed endpoints for explorers ride on the same data (§12). @@ -299,30 +302,31 @@ container that stamps the SEP-58 fields into the Wasm) and [#2586](https://github.com/stellar/stellar-cli/pull/2586) adds `stellar contract verify` (re-run the recorded build locally and byte-compare). Those commands are deliberately local and one-to-one: every verifier pays the full -rebuild cost and no verdict persists for anyone else. **Our service is the +rebuild cost and no verdict persists for anyone else. **Soroscan Verify is the shared aggregation layer above that capability, not a competitor**: a contract built with `--verifiable` is submittable to `POST /v1/verifications` with zero extra metadata, the hosted rebuild runs in the same recorded image, and the signed verdict becomes queryable by everyone — once, instead of once per consumer. `stellar contract verify` remains the trust-nothing local fallback. **We commit to supporting whichever CLI↔service interaction shape -SDF names** — service-mediated submission or on-chain result discovery — and -will contribute the corresponding CLI integration in milestone M4. +the ecosystem standardizes** — service-mediated submission or on-chain result +discovery — and will contribute the corresponding CLI integration (roadmap +phase 4). **Web.** Paste a contract ID; the service reads `contractmetav0`, pre-fills the SEP-58 fields, and the submitter confirms or supplies the source inputs. The same form takes a tarball upload for modes 2 and 3. -**Retroactive verification — a stated RFP priority.** Contracts deployed -before SEP-58 tooling have no embedded metadata, and non-upgradable contracts -never will. For these, `POST /v1/verifications` accepts the full SEP-58 field -set **in the request body as an off-chain metadata submission**. The verdict -is exactly as strong as the embedded case, because embedded fields were never -trusted — only tested: the rebuild either reproduces the deployed bytes or it -doesn't. The record notes the claim channel (`offchain-submission` vs -`contractmetav0`) so consumers can tell where the *claim* came from while -relying identically on the *evidence*. This is how the existing population of -deployed contracts becomes verifiable without redeployment. +**Retroactive verification.** Contracts deployed before SEP-58 tooling have +no embedded metadata, and non-upgradable contracts never will. For these, +`POST /v1/verifications` accepts the full SEP-58 field set **in the request +body as an off-chain metadata submission**. The verdict is exactly as strong +as the embedded case, because embedded fields were never trusted — only +tested: the rebuild either reproduces the deployed bytes or it doesn't. The +record notes the claim channel (`offchain-submission` vs `contractmetav0`) so +consumers can tell where the *claim* came from while relying identically on +the *evidence*. This is how the existing population of deployed contracts +becomes verifiable without redeployment. **Docs-to-verified in under 15 minutes.** We ship a quickstart with an explicit budget — from landing on the docs to a green `FULL_MATCH` on a @@ -363,13 +367,12 @@ model starts there. re-submissions return the existing job, and capped by queue depth with honest `429` responses. Reads are immutable once signed, hence CDN-cached — query load never touches build infrastructure. -- **Third-party audit.** A security audit coordinated by SDF through the - **audit bank** before production launch, scoped to the sandbox, the - signing/registry layer, and the API. The report and resolved findings are a - milestone deliverable (§11); this threat model is a living document in the - repo for auditors to attack. +- **Third-party audit.** An independent security audit before the production + launch, scoped to the sandbox, the signing/registry layer, and the API, + with the report and resolved findings published. This threat model is a + living document in the repo for auditors to attack. -## 9. Operations & sustainability +## 9. Operations **Availability.** The query API targets **99%+ uptime** by separating read and build paths: reads come from a replicated database behind stateless API @@ -387,29 +390,27 @@ the budget. queue depth, build duration, and verdict-rate anomalies (a `NO_MATCH` spike across many contracts is an early non-determinism alarm); alerting on SLO burn; a public status page; an operational runbook covering RPC outages, -registry outages, backpressure, and allowlist rollback. Bleu staffs on-call -through the grant and the tail period. +registry outages, backpressure, and allowlist rollback. Bleu staffs the +on-call rotation for the hosted deployment. **Retention and egress.** Verification records are kept indefinitely — small, append-only, compounding in value. Source artifacts are kept for the life of the service per §3's storage rules, with CIDs published so the ecosystem can -co-pin. **Egress for the public API and artifact downloads is owned by Bleu** -through the grant and tail, bounded by CDN caching and IPFS; the runbook -documents the cost model so a future operator inherits a known bill. +co-pin. Egress for the public API and artifact downloads is bounded by CDN +caching and IPFS; the runbook documents the cost model so any operator +inherits a known bill. -**Post-grant ownership.** Bleu operates the hosted service for at least **12 -months after the final milestone** at our own cost, while actively helping -ecosystem operators stand up peer instances — the healthiest end state is one -where ours is one of several (§5). Ongoing maintenance is deliberately small -(image updates, SEP conformance); we will propose follow-on community funding -only if peer adoption hasn't materialized by the end of the tail. +**Long-term ownership.** Bleu operates the hosted service while actively +helping ecosystem operators stand up peer instances — the healthiest end +state is one where ours is one of several (§5). Ongoing maintenance is +deliberately small: image updates and SEP conformance work. -## 10. Prior art & approach justification +## 10. Prior art & design rationale -We reviewed the public repositories below before writing this; the -observations are from their current code and docs. Together they justify -building on our Soroban-native MVP while importing proven patterns, rather -than adapting any single codebase. +The design draws on a review of the existing verification systems; the +observations below are from their current public code and docs. Together they +explain why Soroscan Verify builds on its own Soroban-native core while +importing proven patterns, rather than adapting any single codebase. **Sourcify ([github.com/ethereum/sourcify](https://github.com/ethereum/sourcify)).** Proof that an open, shared verification layer works at ecosystem scale. We @@ -444,18 +445,15 @@ program to trust), and where Solana effectively trusts one hosted verifier, multiple signing verifiers with per-verifier disagreement rendering are our day-one design (§5). -**Stellar Expert's build workflow.** The RFP context names an SDF -experimental prototype; we found no public repo named -`stellar-experimental/contract-verifications` (the obvious URLs 404). The -substantive existing Soroban prior art is -**[stellar-expert/soroban-build-workflow](https://github.com/stellar-expert/soroban-build-workflow)** -(OrbitLens) — the GitHub Actions workflow behind stellar.expert's contract -validation and the origin of SEP-55. It compiles contracts in CI, publishes -releases with the Wasm and its SHA-256, and uploads GitHub artifact -attestations; validation checks the attestation's builder ID against the -trusted workflow path. Three observations shaped this proposal. The trust -chain is contract → GitHub attestation → GitHub Actions runner — nobody -independently rebuilds, and the SEP discussion +**Stellar Expert's build workflow +([stellar-expert/soroban-build-workflow](https://github.com/stellar-expert/soroban-build-workflow)).** +The existing Soroban prior art (OrbitLens) — the GitHub Actions workflow +behind stellar.expert's contract validation and the origin of SEP-55. It +compiles contracts in CI, publishes releases with the Wasm and its SHA-256, +and uploads GitHub artifact attestations; validation checks the attestation's +builder ID against the trusted workflow path. Three observations shaped this +design. The trust chain is contract → GitHub attestation → GitHub Actions +runner — nobody independently rebuilds, and the SEP discussion ([#1573](https://github.com/orgs/stellar/discussions/1573)) notes attestations don't prevent build-time injection. It is single-surface: stellar.expert is effectively the lone validator, and GitHub a single point @@ -479,50 +477,49 @@ solana-verify's service half (OtterSec's backend) is not the open-source part; the SEP-55 workflow produces attestations, it doesn't rebuild. The chain-specific pieces we need — RPC reading, `contractmetav0` parsing, verdict logic, the pinned rebuild container — are already written and tested -in our repo. +in this repo. -## 11. Milestones +## 11. Roadmap -Mapped to the RFP deliverables list; each has an objective completion test. +Each phase has an objective completion test. -**M1 — Audit-ready service codebase.** The open-source, self-hostable -codebase feature-complete for audit: result signing (§5), allowlist -enforcement with trust tiers and downgrade semantics (§4), all three source -modes with IPFS and the artifact store (§3), sandboxed workers (§8), written -threat model. *Done when:* a third party can stand up a full verifier from -docs alone and verify the MVP fixture end to end; audit scope agreed with the -SDF audit bank. +**Phase 1 — Self-hostable verifier core.** The codebase feature-complete and +audit-ready: result signing (§5), allowlist enforcement with trust tiers and +downgrade semantics (§4), all three source modes with IPFS and the artifact +store (§3), sandboxed workers (§8), written threat model. *Done when:* a +third party can stand up a full verifier from docs alone and verify the MVP +fixture end to end. -**M2 — Security audit and hosted deployment.** Audit through the SDF audit -bank, report published, findings resolved; the hosted service live on +**Phase 2 — Security audit and hosted deployment.** Independent audit with +the report published and findings resolved; the hosted service live on **testnet and mainnet**, including the retroactive submission path. *Done when:* the audit report and remediations are public and `GET /v1/contract/{id}` answers for both networks in production. -**M3 — Stable API, SDK, and docs.** `/v1` frozen and documented with OpenAPI; -the client SDK published, conforming to the verifier-API SEP if authored by -then, otherwise to `/v1` with SEP migration as a standing commitment (§6); -the allowlist policy and governance process published. *Done when:* an -integrator can go from docs to rendering verification state without +**Phase 3 — Stable API, SDK, and docs.** `/v1` frozen and documented with +OpenAPI; the client SDK published, conforming to the verifier-API SEP if +authored by then, otherwise to `/v1` with SEP migration as a standing +commitment (§6); the allowlist policy and governance process published. *Done +when:* an integrator can go from docs to rendering verification state without contacting us, and the under-15-minute walkthrough passes in CI against the live deployment. -**M4 — Integrations.** The badge endpoint, explorer embed, and SDK example -(§12); integration docs; the CLI interaction in whichever shape SDF names -(§7); at least one reference integration landed with Stellar Lab or a -cooperating explorer/verifier. *Done when:* a named partner surface renders -our results in production for both networks. +**Phase 4 — Integrations.** The badge endpoint, explorer embed, and SDK +example (§12); integration docs; the CLI interaction in whichever shape the +ecosystem standardizes (§7); at least one reference integration landed with +Stellar Lab or a cooperating explorer/verifier. *Done when:* a partner +surface renders our results in production for both networks. -**M5 — Production handoff.** Operational runbook published; monitoring, -status page, and on-call in steady state; retention/egress cost model -documented; the 12-month post-grant tail and peer-operator support program -begun (§9). *Done when:* SDF accepts the runbook, SLO dashboards are public, -and at least one external party has a peer verifier running or in progress. +**Phase 5 — Production operations.** Operational runbook published; +monitoring, status page, and on-call in steady state; retention/egress cost +model documented; peer-operator support under way (§9). *Done when:* SLO +dashboards are public and at least one external party has a peer verifier +running or in progress. -## 12. Integrations plan +## 12. Integrations The service is only useful where users already look, so reference -integrations are deliverables: +integrations are part of the roadmap: - **Badge endpoint** — `GET /v1/badge/{contractId}.svg`: a cacheable SVG with verdict and trust tier, for READMEs and explorer pages, with per-verifier @@ -534,26 +531,26 @@ integrations are deliverables: package) wrapping the four endpoints, with the trusted-verifier policy built in and a worked contract-ID-to-summary example. -During the grant we will engage the partners the RFP names — **OrbitLens / +We plan to engage the ecosystem teams closest to this problem — **OrbitLens / Stellar Expert**, **Aha Labs / rgstry.xyz**, **57B**, and **Stellar Lab** (whose Contract Explorer already shows SEP-55 attestation state and is the -natural first surface for the rebuild verdict beside it) — to land the M4 -reference integration and to fit the embed and SDK to how their products -actually consume data. Stellar Expert, given OrbitLens's role in originating -SEP-55, is also a natural early *peer verifier*, not just a consumer. +natural first surface for the rebuild verdict beside it) — to land the +phase-4 reference integration and to fit the embed and SDK to how their +products actually consume data. Stellar Expert, given OrbitLens's role in +originating SEP-55, is also a natural early *peer verifier*, not just a +consumer. -## 13. Compliance & openness +## 13. Openness - **No KYC, no gated access.** Reads are anonymous and free; submission needs no account — abuse is handled by rate limits and resource caps (§8), not identity. - **Apache-2.0, everything.** Verifier, API, workers, SDK, badges, deployment - config, docs — all public under the license the MVP repo already carries. + config, docs — all public under the license this repo already carries. - **Self-hostable by construction.** One documented deployment brings up a - complete verifier; M1's completion test is that a third party can do it - from docs alone. No closed dependencies, no privileged signer, no calls + complete verifier; phase 1's completion test is that a third party can do + it from docs alone. No closed dependencies, no privileged signer, no calls home. - **Community-operable over time.** Results are portable signed statements - and peer instances are first-class (§5), so the ecosystem can outgrow us - without migration. The best outcome of this grant is an ecosystem that no - longer depends on any single operator — including us. + and peer instances are first-class (§5), so the ecosystem can outgrow any + single operator — including us. That is the intended end state. diff --git a/reader/src/networks.ts b/reader/src/networks.ts index adcc610..41e5151 100644 --- a/reader/src/networks.ts +++ b/reader/src/networks.ts @@ -2,7 +2,7 @@ * Network configuration. TESTNET ONLY for this MVP. * * Mainnet config is intentionally NOT wired up. Adding mainnet here is a - * deliberate, reviewed step (grant milestone M2; see docs/PROPOSAL.md). + * deliberate, reviewed step (roadmap phase 2; see docs/ARCHITECTURE.md). */ export interface NetworkConfig { name: string; diff --git a/reader/src/verify.ts b/reader/src/verify.ts index 73712e8..bf96b79 100644 --- a/reader/src/verify.ts +++ b/reader/src/verify.ts @@ -12,8 +12,8 @@ import { extractContractMetaSection } from "./contractmeta.js"; * - METADATA_ONLY_MATCH: WASM differs ONLY in the `contractmetav0` custom * section (behaviorally identical). Sourcify "partial * match" analogue. (Detected structurally in the MVP; - * a strict XDR section-diff is grant scope; see - * docs/PROPOSAL.md.) + * a strict XDR section-diff is roadmap work; see + * docs/ARCHITECTURE.md.) * - NO_MATCH: hashes differ and the difference is not metadata-only. * - ERROR: could not fetch/compare (network, bad ID, etc.). */