Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Add stellar contract verify command - #2586

Closed
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify
Closed

Add stellar contract verify command#2586
fnando wants to merge 58 commits into
contract-build-verifiablefrom
contract-verify

Conversation

@fnando

@fnandofnando commented May 22, 2026

Copy link
Copy Markdown
Member

⚠️ Depends on #2585 — do not merge until #2585 lands on main. This PR's base is contract-build-verifiable, so once #2585 merges, GitHub will automatically retarget this one to main.

What

Adds a new stellar contract verify subcommand that takes a contract id (--id) or WASM hash (--wasm-hash) to fetch from the network, or a local WASM (--wasm) — the three are mutually exclusive. It reads the SEP-58 build metadata embedded in it (bldimg, source identification, build flags), re-runs the recorded build inside the recorded container image, and byte-compares the result against the original to confirm the WASM is reproducible.

Notable bits:

  • The WASM records source_sha256 (required) and, optionally, a source_uri. Verify fetches the source tarball from the recorded source_uri, or from a --source-uri override (an http(s) URL or a local file path), checks its SHA-256 against the recorded value, then extracts it. The source is always a content-addressed tarball, so there's no runtime dependency on a system git binary.
  • It reuses the --verifiable build machinery rather than reimplementing it: the same source-archive extraction (under the data dir, permission-hardened), the same container run, and the same --meta composition — so the rebuilt WASM's metadata matches the original byte-for-byte.
  • A trust prompt fires before pulling an unrecognized bldimg, and unconditionally for any tarball source (tarballs are never default-trusted). --trust skips it; the prompt overrides --quiet so it can't be silenced by accident. The default trust list is the stellar/stellar-cli image.
  • The materialized source directory is created with hardened permissions so a local attacker can't slip a file in mid-verify.
  • Integration tests cover the full loop without any network clone: scaffold with contract init, build verifiably, regenerate the matching archive with contract archive, verify it via --source-uri, plus verify-by---id and a tampered-bytes failure case. They live in the existing integration tier.

Why

#2585 makes it possible to produce a verifiable build; this PR is the other half — a way to check a deployed contract against its claimed source. Together they close the SEP-58 loop end-to-end in the CLI.

Known limitations

N/A

@github-project-automationgithub-project-automationBot moved this to Backlog (Not Ready) in DevXMay 22, 2026
@fnandofnando moved this from Backlog (Not Ready) to In Progress in DevXMay 22, 2026
@fnandofnando self-assigned this May 22, 2026
@saimeunt

Copy link
Copy Markdown

Hi @fnando, we just submitted a proposal for the Contract Source Verification Service RFP and it mentioned your draft PR.

It’s great that SEP-58 core verification logic is now embedded directly in stellar-cli, it provides a single source of truth for build reproducibility among verifiers. We plan to use stellar contract verify in our service to handle the actual verification, which includes fetching WASM, extracting metadata, rebuilding contract with the correct buildimg and comparing rebuilt WASM hash with on-chain hash.

While designing our tech architecture document for the RFP, we noticed retroactive verification is a mandatory requirement for the upcoming verification service, but contracts deployed before stellar contract build —-verifiable lack the proper contractmetav0 containing the SEP-58 fields expected by stellar contract verify.

At the moment, stellar contract verifywill error when SEP-58 fields are missing (as per WIP implementation). This means in its current state the core verification logic from stellar-cli can’t be leveraged to implement retroactive verification using the same ideal path as newly-deployed contracts built with with stellar contract build —-verifiable.

In your notable bits, you mention that the user can supply a local file or URL via --tarball-urlfor supplying tarball_url when it’s missing in contractmetav0, eg. when working with closed-source contracts. We think this should be extended to all SEP-58 fields to allow supporting a wider range of verifications scenario, providing an escape hatch for contracts deployed before SEP-58 is adopted.

It is the responsibility of the verifier to flag these verification results as having their build environment provided by the user submitting the verification and not recorded on-chain in WASM metadata.

Does this suggestion makes sense and do you plan to add support for user-supplied build environment + source identification parameters while finalizing this draft?

@fnando

Copy link
Copy Markdown
MemberAuthor

@saimeunt Yes, the scenario makes sense, but I don't think it should live under stellar contract verify.

The way I see it, the verification service is the one responsible for receiving and storing everything needed to build the contract (source code, OS distro, build options, etc.). For that reason I'd keep the SEP-58 path in the CLI strictly tied to the immutable, on-chain contractmetav0, so its output means one specific, auditable thing, and have the service own its build+verification pipeline for everything else.

The real distinction isn't whether the build image is custom (an on-chain metadata can reference a custom image too, and that's equally hard to trust without auditing the image). It's chain of custody: on-chain fields are immutable and tied to the deployer, while user-supplied fields have no such provenance. That's exactly the case you raised; it's the verifier's responsibility to flag those results as "build environment provided by the submitter, not recorded on-chain."

I'd put all of that under out-of-band verification: the registry works with developers to verify contracts and covers everything SEP-58 doesn't (private source, pre-SEP-58 contracts1, etc.). The registry marks the contract verified, and it's ultimately up to users to trust the registry's findings, so the more verification services a developer goes through, the higher the trust.

Footnotes

  1. pre-SEP-58 contracts with public source are, hopefully, the easiest to handle in such a pipeline, especially if they weren't built with a cargo install-generated CLI, because matching distro + stellar-cli + rust + git revision + build opts will likely reproduce the same wasm 🤞.

@leighmcculloch

Copy link
Copy Markdown
Member

A verification service doesn't need to use the cli to verify with data not in the wasm meta. It should use the docker image directly.

@saimeunt

Copy link
Copy Markdown

@fnando@leighmcculloch Thanks for your answers, I agree stellar-cli and the stellar contract verify command is strictly a developer-facing CLI that can be used to locally verify a contract and check against what verifiers report.

Verifiers will use their own logic built on top of the stellar-cli Docker image to actually verify contracts and support more scenarios than the standard SEP-58 path.

@fnando
fnandoforce-pushed the contract-build-verifiable branch 2 times, most recently from 557647b to 6e7c125CompareJune 17, 2026 19:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6e7c125 to 1780110CompareJune 18, 2026 23:47
@saimeunt

saimeunt commented Jun 26, 2026

Copy link
Copy Markdown

@fnando@leighmcculloch Following up on testing stellar contract verify inside a verifier, I built a PoC verifier leveraging the CLI for build reproducibility: https://github.com/walnuthq/stellar-source-code-verification/blob/main/apps/api-verifier/src/routes/verify.ts#L18-L28

It's live on Cloudflare and implementing the upcoming verifier API SEP: https://stellar-source-code-verification-api-registry.walnut.dev/wasms/3d67301ba90bdbdf712a75bcf0061193f481958a5095a29c90465a3529254bcf.json

While I agree it's not the responsibility of the stellar-cli to enable every use cases that a verifier should cover (out-of-band verifications, closed sources, legacy contracts, etc), the stellar contract verify logic works really well for the "common flow" covered by SEP-58 (contracts built using the correct fields embedded in WASM).
This command could be use in CI/CD (eg. to immediately verify a built contract using stellar contract build --verifiable indeed verifies with stellar contract verify as part of a release workflow) and would face the same error I ran in (stellar-cli#2585), namely failing to read materialized sources from the build image.

Maybe we should stop writing into the bind mount at all: mount /source read-only, set CARGO_TARGET_DIR to a container-writable path (tmpfs or anon volume), and read the rebuilt wasm back over the API (download_from_container) instead of from host /source/target. This would sidestep the ownership problem entirely and match the intuition that verify must not mutate the source it's checking.

@socket-security

socket-securityBot commented Jul 9, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Addedcargo/​zip@​8.6.010010093100100

View full report

@fnando
fnandoforce-pushed the contract-verify branch 2 times, most recently from 7f2666e to 76cf43cCompareJuly 9, 2026 21:31
@fnando
fnandoforce-pushed the contract-build-verifiable branch from 6018142 to 26bf741CompareAugust 17, 2026 12:53
@fnando

Copy link
Copy Markdown
MemberAuthor

We've decided that we won't ship this command as part of the CLI. We've extracted this as an experimental plugin, which should not be used in production.

https://github.com/stellar-experimental/stellar-contract-verify-plugin

@fnandofnando closed this Aug 27, 2026
@github-project-automationgithub-project-automationBot moved this from In Progress to Done in DevXAug 27, 2026
@fnando
fnando deleted the contract-verify branch August 27, 2026 18:59
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants

@fnando@saimeunt@leighmcculloch