Contract Source Verification using Docker without Attestation #1923
Replies: 24 comments 87 replies
|
Great complement to SEP-55 👍 Random stuff: On Do we keep the attestation generation outside of this? Would make sense if we look at the Build L2 from SLSA I guess. Still would be nice to consider being able from the CLI to check a wasm with an attestation. Tangentially, do we care at all about SBOM for building WASM. Attestations and SBOM go together usually. |
|
With this proposal, why should we care about SEP-55? I understand they have different goals, but what we always wanted was Source Verification to begin with. I think Source Verification should deprecate Build Verification for new contracts, but Lab and explorers can (are recommended to) support Build Verification status as a fallback. |
|
Thanks for opening this discussion! I think this is a necessary step towards getting repeatable/verifiable contract builds. Left comments on the CLI PR, but overall agree with the direction. |
|
Looks like a very solid proposal backed by a serious research 🧐 👏 My primary concern here is the simplicity. It should be dead simple to use and almost impossible to make mistakes with, otherwise we'll see a lot of complaints about the building process and incompatibility (if you open even a tiny hole for the users to make mistakes, they will definitely use it). E.g. build parameters can be simplified significantly if we assume some defaults.
I also think that defining different "classes" makes this proposal irrelevant. What's the real value in having classes B and C if they can't be reliably and unambiguously reproduced in the future? There's literally zero value in the verification process that results a smart contract be marked as verified in some wallets but non-verified in explorers or vice versa? Lastly, agree with @fnando. This SEP should be either merged with SEP55 or substitute it. |
|
Thanks for sharing this early, the approach looks solid. A couple of practical concerns from our perspective:
The updated draft spec covers both via Class C and the verification registry pattern in Appendix A.3, this is exactly what's needed. We'd just want to make sure this path remains a first-class citizen in the final spec and that explorer integrations support registry-verified contracts alongside self-verified ones. Happy to provide more feedback on the registry interface as the discussion evolves. |
|
@willemneal @chadoh I think we should keep an eye on this for what we are building with https://rgstry.xyz/ For context here. We are working with a few community partners on a contract registry. This project is also getting some support from SCF. |
|
Hi all, the feedback has been very helpful. I have revised the draft specification to incorporate feedback, reduce the number of unique meta that needs to be stored, remove unnecessary concepts like classes, and focus on the build environment information that's required to succeed at reproducing a .wasm build from source, since that seems like the most critical component to validate and agree on. And the spec continues to keep the implemenation options open to a variety of setup: open automation, closed verification registries, pre and post deployment verification, etc. The specification is now less prescriptive about how tooling functions and just describes the basic mechanism of using docker/containers to replay that build environment information. The mechanism describe in the spec can be automated by cli tooling, explorers, and verification registries. Does this seem sufficient to everyone? |
|
Hi, wanted to share a deployer's perspective on bldimg. The spec already flags this in Security Concerns, but I think it deserves stronger treatment than a A verifier that replays whatever
Both are inherent to letting |
|
Thanks @leighmcculloch for rewriting the spec based on the feedback. Something that isn't clear to me is who will be the users of custom build images. From my point of view, SDF should be the only entity providing standard minimalistic images that can be further used in the release purpose and code verification. We are ready to employ this standard for the automated source code verification in StellarExpert, yet we will never use arbitrary images published by someone except for SDF or our company. First of all, it's potentially not safe for the host env (recent CVE-2026-31431 and CVE-2025-31133 showed that container isolation might be not as bulletproof as people think). Ultimately, it's not safe by nature -- running arbitrary code (even in the container) not a good idea, it can lead to all sort of attacks (third-party DDoS, resource exhaustion, Monero mining, spam/virus re-distribution, etc.) And since we plan to automatically execute the verification procedure for all contracts that get uploaded to the ledger, it poses a significant security threat for us. I think the same applies to any other explorer/indexer/wallet/app that is going to automatically perform the verification either triggered by on-chain events or on-demand. Regarding multiple container images for different architectures or with different compiler toolchains. I still think that there should be only one image per Soroban SDK version (automatically assembled with the latest stable Rust toolchain upon the new SDK release) -- not entirely convinced that people really need arm64 image for this particular purpose. Because this release mode with containerized compilation will likely be used only a few times per project since people will not be using it during development or for running tests -- it's simply not needed for any other situation, contract compilation before publishing on the Mainnet is the only use-case for it. And for such rare occasions they can survive the inconvenience of using a single standard amd64 image instead of one optimized specifically for their architecture. Lastly, regarding custom pipelines or verifying contracts without a public repo (classes B and C in the original notation). I'll reiterate on this -- only contracts with public repository can be verified automatically using routines described in this SEP. Everything else should be outside of the scope. @tupui pointed out on DORA and other requirements that may imply executing verification over the closed source. But what is the practical impact for the ecosystem in this case? StellarExpert won't display any verification status for such a contract (as we cannot automatically verify it), the same goes for any other ecosystem apps/wallets. So it's a verification that nobody can actually validate, and therefore it's useless for wider public. I firmly believe that any process that involves ambiguity or human in the loop should be excluded from this SEP. If a third-party auditor ready to testify that the smart contract code was compiled from a given source (and it's up to them what processes, toolchains, env varaibles do they use to verify that), they can simply sign a proof (it can be a JSON string with verification details: source, toolchain version, owner company, etc) and we will pull this data from the TOML file. This way we can establish a trust chain This is an awesome SEP that addresses a long-standing problem. I've been working on this problem for more than 2 years and really happy to see where all this is moving. Let's just make this SEP a bit more straightforward and we'll start using it right away. |
|
I've taken the draft specification I previously posted at #1923 (comment) and propose it be included in the stellar-protocol repo as SEP-58, still in draft for tweak during implementation: The only modifications I made were clarifying and filling missing guidance:
[Edit: Added point 3 and 4.] |
|
Following up the review I left here #1933 (comment) and opening up a point about schema for URL/URIs and protocols. There is some wording in the design rational section about the SEP not prescribing this too much. Still, in the specification part we are giving examples of regex with HTTPS URLs and GitHub. I am concerned that this will lead in practice systems to only understand HTTPS + GitHub and flag all the rest. I think we should strive to be more agnostic. As with everything, in practice that still means support tiers. A minima, I suggest that IPFS be on the same support level as HTTPS. For GitHub, we almost don't need to mention the forge itself. |
DetailsIGNORE THIS SECTIONAn internal discussion brought up an interesting point: given that we may have many vendors running verification and displaying the status (¡viva decentralization!), the file digest hash could diverge for some reason (mostly due to misconfiguration of the verification process, but could also be one of the vendors being hacked). One of the suggested ideas was making [[CONTRACT_VERIFICATION_HASHES]]
C…ID="sha256:<sha>"Then interfaces could have something like: $ stellar contract verify --id C…ID
Publisher https://example.com advertised contract as sha256:<sha>"
[√] Verified by Stellar Expert
https://stellar.expert/explorer/public/contract/C…ID
[√] Verified by Rgstry
https://mainnet.rgstry.xyz/contracts/C…ID
[!] Verified by ACME — Diverged from advertised
https://example.com/mainnet/contracts/C…IDThis also helps with contracts that have private code, because you have some indication of what's the published contract by checking the toml file. Is this something we should consider? Edit: This doesn't make sense, now that I think about it. It must match the deployed contract and nothing else. The flow for displaying statuses would be:
Anything mismatching would display an error message: $ stellar contract verify --id C…ID
Publisher https://example.com/ advertised contract as sha256:<sha>"
[√] Verified by Stellar Expert
[https://stellar.expert/explorer/public/contract/C…ID](https://stellar.expert/explorer/public/contract/C…ID)
[√] Verified by Rgstry
[https://mainnet.rgstry.xyz/contracts/C…ID](https://mainnet.rgstry.xyz/contracts/C…ID)
[!] Mismatching verification by ACME
[https://example.com/mainnet/contracts/C…ID](https://example.com/mainnet/contracts/C…ID) |
|
Hi everyone, We've been following this thread closely and wanted to share where we've landed, since we're putting together a proposal for the contract source verification RFP and the build-environment details being worked out here directly shape what we're building. For context, we have a working prototype: a rebuild-based verifier for Soroban. Someone submits their source, we rebuild it inside a pinned container, and we check whether the resulting Wasm hash matches the bytecode on the ledger. Results go out through a free, open API so explorers, wallets, and Lab can show a "source verified" badge without rebuilding anything themselves. It's open source and self-hostable, no KYC. A few things specific to the discussion here: On image trust. We're aligned with the direction this thread has been converging on, that a verifier defines its own trust model. Ours only rebuilds using SDF-sourced, digest-pinned images from an allowlist; we don't run arbitrary deployer-supplied images, for the safety reasons orbitlens raised earlier. We report the image trust signal alongside the match result (e.g. "built with an SDF-maintained image" vs "image not on our allowlist"). If SDF publishes official stellar-cli images with attestation and SBOMs, that fits us well: bldimg can stay free-form while verifiers still report a meaningful signal, much like the multidimensional direction chan-lz described. On bldimg and bldopt. We read both from contractmetav0. Today bldimg selects the allowlisted image we rebuild in. Applying bldopt (passing the recorded options through to stellar contract build on replay, and recording the image digest plus options used so the result is independently reproducible) is on our near-term roadmap rather than something we run yet. Collapsing the spec to just those two fields works cleanly on our end and keeps that work small. On already-deployed contracts. Since contracts can't be redeployed to add metadata after the fact, we also handle contracts without on-chain SEP-58 fields: source can be submitted out of band (direct upload, hosted tarball + sha256, IPFS, or hash-only) and verified against the deployed Wasm. We'd like this path to stay first-class in the final spec, alongside the registry pattern Chupsy and tupui raised for surfacing this info post-deploy. We recognize this is contested: orbitlens argued that contracts without public, independently reproducible source shouldn't be verifiable under the SEP at all, partly because that path can't be automated and falls back to manual checks. Our take is that a service layer is what makes registry-based verification of these contracts automatable in the first place, which directly addresses that concern. On multiple verifiers disagreeing. We treat each verifier's result as a signed, attributable signal and aggregate them into a consensus view (verified, divergent, mismatch, etc.). There's deliberately no single global authority deciding what's "correct" — consumers pick which verifiers they trust. We can line this up with whatever submission and discovery shape SDF decides on, service-mediated or more on-chain. We see rebuild verification and SEP-55 attestation as complementary signals rather than either/or, and we'll conform our API to wherever the spec lands. Two things we'd genuinely like input on:
|
|
I'm also looking at the RFP for contract verification and have managed to validate some deployed contracts using the image build scripts from A question on what gets stored, since you asked for feedback there: how is The spec frames StellarExpert already treats that as first-class; its verification record surfaces {
"validation": {
"status": "verified",
"repository": "https://github.com/AquaToken/soroban-amm",
"commit": "c4d842de3108a23a4a1107b7d54c357bae45f962",
"package": "soroban-liquidity-pool-router-contract",
},
}(You can check this yourself via
Two concerns with leaving it inside
Suggestion: give "which contract" a canonical source-identification field in §2, alongside |
|
Hi everyone, we’re Walnut and just submitted a proposal for the Contract Source Verification Service RFP. Our approach is focused on providing a registry of verified contracts rebuilt using the core verification logic and build reproducibility explained in SEP-58 Appendix A. We plan on leveraging on-going work (#2586) on the
Our public verifier API write endpoint will rebuild submitted contracts and persist verified results (both WASM-hash match) in a DB as well as SEP-58 fields and contract sources. Verified contracts results are available as cheap lookups by calling our API read endpoints. Our approach to decentralization is to provide an open-source, easily self-hostable service to let multiple verifiers host their instance and aggregate results in a separate, dedicated endpoint. The verifiers making their way to the aggregated endpoint should be whitelisted by the SDF and anyone can apply as long as their service adhere to the upcoming verifier-API SEP. We acknowledged retroactive verification for contracts deployed before SEP-58 adoption is mandatory and we think a correct approach would be to include an escape hatch in Any comments or feedback is appreciated! |
|
@leighmcculloch while writing this response, something came to me… the Could we drop For git, only |
|
Hi everyone, we’re CSV Verify a Himera Consulting team. We just submitted a proposal for the Contract Source Verification Service RFP. Our approach is focused on a public Soroban source verifier for Stellar: |
|
Hey everyone, I'm Will — part of the CSV Verify Wanted to add some context from the user-facing The problem he describes — "metadata reads work
From the UI side, it translates like this: user We're also aligning our read endpoints with the Happy to share more implementer feedback on |
|
I have been working heavily on verifying existing untagged contracts and have had some success. While checking deployed wasm on mainnet, I noticed only about 1000 of the 3600 wasm blobs had cliver (while almost all have rsver). This could be due to a stellar-cli build with version < 23.2.0 when it was not yet added to the metadata. However, there are many older repos (from 2024 and 2025) that use I figured this could easily be supported by a slightly modified rust docker image (adding the wasm32-unknown-unknown toolchain via rustup, and setting "cargo" as the entrypoint, but I hit an issue. The current SEP-0058 spec requires passing "contract build" as hardcoded arguments, see the example: $ docker run --rm -v "$PWD:/source" -e RUSTUP_TOOLCHAIN "$IMAGE" \
contract build \
--manifest-path=contracts/foo/Cargo.toml \
--optimizeThis makes it impossible to swap out a rust image there and do a cargo build. I have two potential ideas how to solve this and would love feedback:
Of course this will break any existing images with the draft spec but that is an existing issue that will have to be addressed anyway and it should be pretty easy to programmatically tag these after the fact. |
|
Another issue that came up while trying to find the source code for existing untagged repositories is the use of I would propose adding a To make this more concrete, here are two examples:
If this is too complex a change, these cases could be discounted and must deploy newer contracts without I do think it is worth a discussion on how to handle these cases. However, the cargo vs stellar args issue above is a lot more common and affects maybe half the actively used deployed contracts on mainnet. |
|
I'm posting a small update to SEP-58 that doesn't change the functional side of it, but makes part of the design rationale clearer that I realise on re-read of the proposal is not explicit: This design rationale is the context behind statements I made at #1923 (reply in thread) and #1923 (reply in thread). |
|
@fnando One thing I'm not sure if we've quite nailed down is when verifying a contract if For example, in SEP-58 it examples passing stellar-protocol/ecosystem/sep-0058.md Lines 409 to 417 in 597d215 There are meta fields written from:
Only meta from (2) should be repeated, otherwise multiple duplicate meta entries will appear. Even if duplicate meta entries are automatically reduced to a single entry doing so may result in the meta being encoded in a different order resulting in a different wasm hash. Also, with regard to supporting non-stellar-cli builds such as those exampled in #1965, a How are the prototypes of the stellar-cli handling the meta in each of the three cases above? |
|
One open question listed here is how already-deployed contracts without embedded metadata get discovered and verified. A small observation from the consumer side: discovery and verification may deserve to be decoupled. Discovery (which deployed WASM hashes exist, which have any verification claim from any verifier, which have none) is a pure read problem that needs no trust decisions and could run today against the existing surface. Verification of the uncovered remainder then becomes a prioritized worklist rather than an unknown. Publishing that coverage number periodically would also give this SEP a progress metric: the fraction of live mainnet bytecode with at least one claim is currently unknown, and it is hard to improve a number nobody measures. We are looking at building exactly this census layer as a consumer of SEP-58 vocabulary and the registry API being discussed in #1945, and would welcome pointers if someone is already measuring it. |
|
Following this thread with interest. We're building soroverify, a public verification service aimed at the SCF RFP for this same problem, and wanted to share something concrete instead of just saying "we're building this too." On the bldimg trust question (@chan-lz's May 8 comment, and @mootz12's point on the CLI PR): we ran into this before finding this thread, landed on the same worry, and already have a working answer for it. Our verifier only replays a bldimg that's on an explicit, digest pinned allowlist. If the allowlist is empty or unset, everything gets rejected, nothing gets trusted by accident. This is built, tested (we have a test that checks zero exec calls happen for a disallowed image, not just that the response comes back as an error), and running against a real testnet contract right now. Happy to share the details if it's useful for how bldimg guidance ends up written into the spec. It's a SHOULD in the draft at the moment, and from actually building it, it didn't add much operator burden, so it might be worth being a stronger requirement. On the sub thread between @chan-lz and @leighmcculloch about opaque or hostile build images: we don't have an answer for "the image itself is malicious" beyond the allowlist either, same gap named in the thread. But we think having several independent verifiers publish signed results for the same wasm hash helps in practice, since nobody has to trust one operator's allowlist alone. That's built into our design from the start, results are signed per verifier and keyed by wasm hash, and the API returns every verifier's result for a hash instead of assuming there's only one. Running your own verifier is meant to be a real, documented option, not an afterthought. Closer to the socket.dev style multi dimensional signal @leighmcculloch mentioned than a plain yes or no. On @Chupsy's point about post deployment verification: this is the main thing we're solving for, not a side case. Since we verify by rebuilding submitted source against a hash that's already deployed, older non upgradable contracts work fine without any special handling. We checked this live against a contract we deployed ourselves to testnet and it just worked. Is there a writeup anywhere of the "verification registry pattern" in Appendix A.3, or is the thread and draft spec the best source right now? On the meta spec itself: we built against an earlier version of the draft (separate bldopt_profile and bldopt_optimize fields, rsver kept apart from bldimg) and saw @leighmcculloch's May 8 update collapsing that into one repeating bldopt field and dropping cliver. We're updating our side to match. Is there a good way to get notified of future spec changes besides just watching this thread? Repos if it helps, both have real commit history and tests behind them, not just a proposal: https://github.com/soroverify/soroverify-verifier, https://github.com/soroverify/soroverify-consume. Happy to go deeper on any of this, or open a separate thread if that's better than piling on here. |


Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Context: Existing standards and previous discussions about contract build and source verification:
👋🏻 Conversations have arisen from time to time about finding a method for verifying contracts without having completed attestation. A previous discussion and experiment (#1802) was much along those lines. This thread is to discuss the possibility of using Docker / Containers like other blockchains do.
Below is a rough prototype introducing docker into the
stellar contract buildcommand so that by when building contracts they are reproducible, and astellar contract build verifycommand that can replay a build with an identical environment as the original to reproduce an exact binary. It's intended to be complementary to the existing build verification process defined in SEP-55.The implementation is here: stellar/stellar-cli#2525. It is a wip under active development and may change significantly.
You can try it out with:
Then build your contract:
Then verify the build:
The tl;dr is the
stellar contract buildkicks off thestellar/stellar-clidocker image and writes enough meta to reproduce it in the same environment with the same tools. Duringstellar contract build verifythat meta is used to replicate the original build environment.The cornerstone is the meta which is captured in this very rough draft spec: draft-specification.md
There's a contract wasm on mainnet that used the build process and passes with the verify process:
e7089d989d23d43e03b169b5be9649bc4f67c67685b8bc042da0adac21a4eb08(meta.json)As an example of how verification could run, I've created a repo with a ci workflow that gets all mainnet contracts and attempts to verify them all on a schedule, and you can see the contract wasm above is verifiable:
e7089d989d23d43e03b169b5be9649bc4f67c67685b8bc042da0adac21a4eb08.json (GitHub Actions Run)
I'm sharing all of the above while it is still very raw, rough, and not particularly polished. But, I wanted to share this early to get early feedback. Other than general feedback, I'm specifically interested in what people think about the meta that gets stored.
All reactions