Skip to content

Voter Verification Guide

This guide explains how to independently verify that a proposed canister upgrade in the OHSHII-LAUNCHER DAO was built from the claimed source code. NEVER trust proposal text alone - always verify before voting.

CRITICAL: A malicious proposer can write anything in the proposal title and description. The ONLY way to know if a proposal is legitimate is to:

  1. Clone the source repository
  2. Checkout the exact commit referenced in the proposal
  3. Review the code changes
  4. Build with the sandboxed Docker script
  5. Compare your locally computed hash with the proposal’s hash

If the hashes don’t match, DO NOT vote for the proposal.


Before you begin, ensure you have:

  • Git installed
  • Docker installed and running (for backend verification)
  • dfx installed (for frontend evidence verification) - Install dfx
  • Node.js and npm installed (for frontend verification)
  • Terminal/command line access
  • ~20GB disk space for Docker build cache (first build only)

Use the one-click verification script:

Terminal window
# 1. Clone the repository and checkout the proposal's commit
git clone https://github.com/ohshii-labs/ohshii-launcher
cd ohshii-launcher
git checkout <git_commit_hash_from_proposal>
# 2. Inspect the Dockerfile (IMPORTANT: read it before running)
cat Dockerfile
# 3. Run the verification helper
./Deploy/verify-proposal.sh <canister_name>

The script outputs the .wasm.gz SHA-256 hash. Compare it with the proposal’s WASM Hash.

Valid canister names: ohshii_launcher_backend, dao_storage, pool_manager, ohshii_governance, sons_governance


The verify-proposal.sh script does NOT work for frontend. Use dfx with --compute-evidence:

Terminal window
# 1. Clone the repository and checkout the proposal's commit
git clone https://github.com/ohshii-labs/ohshii-launcher
cd ohshii-launcher
git checkout <git_commit_hash_from_proposal>
# 2. Install dependencies
npm ci
# 3. Compute the evidence hash locally (requires dfx + network access)
dfx deploy ohshii_launcher_frontend --network ic --compute-evidence

This builds the frontend locally, connects to the IC to determine what batch operations would be needed, and computes the Evidence Hash. Compare it with the proposal’s Evidence field. If they match, the proposed assets are built from that source code.

Requires: dfx installed, internet access to IC mainnet. See “Frontend (Asset) Canister Upgrades” below for full details.


Terminal window
git clone https://github.com/ohshii-labs/ohshii-launcher
cd ohshii-launcher

The proposal displays a git_commit_hash. Checkout that exact commit:

Terminal window
git checkout <git_commit_hash>

Important: If the commit hash doesn’t exist in the repository, the proposal may be fraudulent.

Before running any build, read the Dockerfile to ensure it doesn’t contain malicious commands:

Terminal window
cat Dockerfile

Look for:

  • Standard rust:latest or similar base image
  • cargo build commands
  • No suspicious curl, wget, or external downloads
  • No hardcoded private keys or backdoors

The Docker build produces deterministic WASM files:

Terminal window
./Deploy/docker-build.sh

This script:

  1. Builds all 5 canisters in an isolated Docker container
  2. Optimizes WASMs with ic-wasm
  3. Creates deterministic .wasm.gz files with gzip -9 -n
  4. Outputs hashes to Deploy/WASM_HASHES.txt

Build time: ~5-10 minutes on first run (downloads dependencies), ~2-3 minutes on subsequent runs.

Option A: Use the verification helper

Terminal window
./Deploy/verify-proposal.sh <canister_name>

Valid canister names:

  • ohshii_launcher_backend
  • dao_storage
  • pool_manager
  • ohshii_governance
  • sons_governance

Option B: Manual verification

Terminal window
# The hash is already in WASM_HASHES.txt
cat Deploy/WASM_HASHES.txt | grep <canister_name>
# Or calculate manually
shasum -a 256 Deploy/wasm/<canister_name>_docker.wasm.gz

Open the proposal in the OHSHII-LAUNCHER governance interface. Compare:

Proposal TypeCompare your hash with…
Backend UpgradeWASM Hash field
Frontend (Asset) UpgradeContent Hash field

MATCH = The proposed WASM was built from the claimed source code. Safe to vote.

MISMATCH = The proposed WASM is different. DO NOT VOTE for this proposal.


  • Raw .wasm: Uncompressed WebAssembly module
  • Gzipped .wasm.gz: Compressed with gzip -9 -n

DAO proposals use .wasm.gz because:

  1. Smaller size = lower storage costs
  2. Faster upload to the Internet Computer
  3. The -n flag removes timestamps for reproducibility

Without the -n flag, gzip embeds the file modification timestamp:

Terminal window
# WITHOUT -n: different hash every time
gzip -9 -k file.wasm
shasum -a 256 file.wasm.gz # Hash A at 10:00 AM
rm file.wasm.gz
gzip -9 -k file.wasm
shasum -a 256 file.wasm.gz # Hash B at 10:01 AM (DIFFERENT!)
# WITH -n: same hash always
gzip -9 -n -k file.wasm
shasum -a 256 file.wasm.gz # Hash C
rm file.wasm.gz
gzip -9 -n -k file.wasm
shasum -a 256 file.wasm.gz # Hash C (SAME!)
Hash TypeUsed ForDescriptionHow to Verify
WASM HashBackend upgradesSHA-256 of the .wasm.gz module./Deploy/verify-proposal.sh <canister>
EvidenceFrontend upgradesDeterministic hash over all batch operationsdfx deploy ohshii_launcher_frontend --network ic --compute-evidence
Content HashFrontend upgradesSHA-256 of individual uploaded filesshasum -a 256 <file> (simpler alternative)

Backend upgrades use install_chunked_code to deploy compiled Rust canisters.

  1. Proposer builds WASM with Docker
  2. Proposer uploads chunks to proposal storage
  3. Proposal stores wasm_module_hash (SHA-256 of .wasm.gz)
  4. You verify by rebuilding and comparing hashes
  5. At execution, backend recalculates hash from stored chunks (TOCTOU protection)
  6. dfx canister info confirms deployed module hash

After a proposal is executed, verify the deployed canister:

Terminal window
dfx canister info <canister_name> --network ic

The output shows the installed module hash. It should match the proposal’s WASM Hash.


Frontend upgrades use the IC asset canister’s batch upload system (the same used by all SNS DAOs on the Internet Computer). This is a fundamentally different process from backend upgrades.

Important: The verify-proposal.sh script does NOT work for frontend. Use dfx deploy --compute-evidence instead.

Reference: This verification process follows the official DFINITY documentation: SNS Asset Canisters


How Asset Canister Updates Work (Background)

Section titled “How Asset Canister Updates Work (Background)”

Unlike backend canisters (where you upload a single .wasm.gz file), frontend assets (HTML, CSS, JS) are stored inside the asset canister. All asset canisters use the same standard assetstorage.wasm.gz code provided by dfx — so module_hash is the same for every asset canister. What differs is the content stored inside.

The update process works like this:

  1. Proposer (with Prepare permission) runs dfx deploy ohshii_launcher_frontend --network ic --by-proposal
  2. dfx builds the frontend, uploads asset chunks to the canister, and locks the batch
  3. The asset canister computes an evidence hash via compute_evidence() — a deterministic hash over ALL batch operations (file keys, content types, encodings, SHA-256 of each file)
  4. The proposal stores: batch_id, evidence, git_repo_url, git_commit_hash
  5. Voters verify the evidence by rebuilding locally with --compute-evidence
  6. If approved, governance calls commit_proposed_batch() which re-validates the evidence (TOCTOU protection)

When you view an AssetUpgrade proposal in the governance interface:

FieldExampleWhat It Means
Evidencee3b0c44298fc1c...Deterministic hash over all batch operations (you CAN verify this!)
Batch ID42Identifies the locked batch on the asset canister
Git Repo URLhttps://github.com/ohshii-labs/ohshii-launcherSource repository
Git Commitf1e2d3c4b5a6...Exact commit the frontend was built from
Build Instructionsnpm ci && npm run buildHow to reproduce the build

Terminal window
git clone https://github.com/ohshii-labs/ohshii-launcher
cd ohshii-launcher
git checkout <git_commit_hash_from_proposal>
Terminal window
npm ci

npm ci uses the exact versions from package-lock.json, which maximizes reproducibility.

Terminal window
dfx deploy ohshii_launcher_frontend --network ic --compute-evidence

What this command does:

  1. Builds the frontend locally (dfx automatically runs npm run build for asset canisters — you do NOT need to run it manually)
  2. Connects to the IC mainnet read-only to fetch the current state of the asset canister (it does NOT deploy or upload anything)
  3. Determines what batch operations would be needed to go from the current state to the new version
  4. Computes a deterministic hash over those operations (the evidence)
  5. Displays the evidence hash and exits

Why --network ic? The evidence depends on the DIFF between the current canister state and the proposed new state. dfx must read the current state from IC mainnet. Without --network ic, dfx would try a local network where the canister doesn’t exist (causing the “no Wasm module” error). This is a read-only operation — nothing is deployed or modified on mainnet.

Example output:

Computed evidence: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

Step 4: Compare Evidence with the Proposal

Section titled “Step 4: Compare Evidence with the Proposal”
Your Computed EvidenceProposal’s EvidenceResult
e3b0c44298fc1c...e3b0c44298fc1c...MATCH - The proposed assets match the source code
e3b0c44298fc1c...7a8b9c0d1e2f...MISMATCH - Do NOT vote!

The evidence hash is NOT a simple file hash. It is a deterministic hash computed by the asset canister’s compute_evidence() function over the entire set of batch operations:

Evidence = Hash(
CreateAsset { key: "/index.html", content_type: "text/html", ... },
SetAssetContent { key: "/index.html", sha256: <file_hash>, chunks: [...] },
SetAssetContent { key: "/assets/index-abc123.js", sha256: <file_hash>, chunks: [...] },
DeleteAsset { key: "/old-file.css" },
... all operations needed to go from current state → new state
)

This means:

  • Same source code + same current canister state = same evidence (deterministic)
  • The evidence covers ALL files, not just one — it is a comprehensive proof
  • It also includes deletions of old files and metadata changes
  • The proposer cannot influence this computation — it is calculated by the IC

The --compute-evidence flag connects to the IC mainnet because the evidence depends on the difference between the current canister state and the proposed state. Two identical builds would produce different evidence if the canister’s current state is different.

This is a read-only operation. Despite the dfx deploy command name, the --compute-evidence flag prevents any deployment. It only:

  1. Reads the current canister state from IC (query call)
  2. Compares it with your locally built assets
  3. Computes and displays the evidence hash

Without --network ic, dfx defaults to a local network where the canister doesn’t exist, causing the error: "canister contains no Wasm module".

You need:

  • dfx installed (install guide)
  • Internet access to the IC mainnet (read-only)
  • Node.js + npm installed (dfx runs npm run build automatically)

Alternative: Content Hash Verification (If You Don’t Have dfx)

Section titled “Alternative: Content Hash Verification (If You Don’t Have dfx)”

If you don’t have dfx installed, you can do a simpler verification by building manually and checking individual file hashes:

Terminal window
# 1. Install dependencies and build the frontend manually
npm ci
npm run build
# 2. Hash the specific file mentioned in the proposal's Build Instructions
shasum -a 256 dist/index.html

Compare this with the Content Hash if shown in the proposal. This verifies individual files but does NOT cover the full batch (deletions, metadata changes, etc.).


The build is designed to be reproducible — the same commit should produce the same evidence hash:

  • Timestamps are derived from the git commit (not Date.now()), so they are identical for the same commit
  • Dependencies are locked via package-lock.json and installed with npm ci
  • Vite uses strictRequires: true for deterministic CommonJS handling
FactorBackend (Docker)Frontend (dfx)
Build reproducibilityIdentical (Docker isolates everything)Should match if same Node.js major version
Verification toolverify-proposal.shdfx deploy --compute-evidence

Tips if evidence doesn’t match:

  • Use npm ci before running dfx (not npm install)
  • dfx runs npm run build automatically — do NOT run it separately
  • Use the same Node.js major version as the proposer (check node --version)
  • Run git status to ensure no local modifications
  • Run git log -1 --format=%H to verify you’re on the exact commit

If evidence doesn’t match:

  1. Check you’re on the correct git commit
  2. Check your Node.js version matches the proposer’s
  3. Review the source code manually — if the code looks legitimate, the mismatch may be due to build tool differences, not malicious intent
  4. Ask the proposer to specify their Node.js version in the Build Instructions

Install dfx: sh -ci "$(curl -fsSL https://internetcomputer.org/install.sh)"

Possible causes:

  1. Wrong commit: Verify you checked out the exact commit from the proposal
  2. Different Node.js version: Use node --version and compare with the proposer’s
  3. Modified files: Run git status to ensure no local changes
  4. Stale npm cache: Try rm -rf node_modules && npm ci
  5. Different canister state: If the canister was updated between proposal creation and your verification, evidence will differ (this is rare during voting period)

“The build instructions are suspicious”

Section titled ““The build instructions are suspicious””

DO NOT vote if the build instructions contain:

  • curl or wget downloading external files
  • Commands that modify system files
  • Obfuscated or encoded commands
  • Anything you don’t understand

Report suspicious proposals to the community.

”I don’t have dfx and can’t install it”

Section titled “”I don’t have dfx and can’t install it””

Use the simpler Content Hash method described above, and also:

  1. Review the source code at the git commit for suspicious changes
  2. Verify the build instructions are standard (npm ci && npm run build)
  3. Check that the proposer is a known, trusted community member

Manual Verification (Without Helper Script)

Section titled “Manual Verification (Without Helper Script)”

If you prefer to do each step manually:

Terminal window
# 1. Build with Docker
./Deploy/docker-build.sh
# 2. The .wasm.gz files are already created in Deploy/wasm/
ls -la Deploy/wasm/*.wasm.gz
# 3. Calculate hash manually
shasum -a 256 Deploy/wasm/ohshii_launcher_backend_docker.wasm.gz
# 4. If you need to regenerate gzip for some reason:
rm Deploy/wasm/ohshii_launcher_backend_docker.wasm.gz
gzip -9 -n -k Deploy/wasm/ohshii_launcher_backend_docker.wasm
shasum -a 256 Deploy/wasm/ohshii_launcher_backend_docker.wasm.gz

Error: Docker is not running. Please start Docker Desktop.

Start Docker Desktop and try again.

Possible causes:

  1. Wrong commit: Double-check you checked out the exact commit from the proposal
  2. Modified local files: Ensure git status shows no uncommitted changes
  3. Different Rust toolchain: The Dockerfile should use a consistent toolchain
  4. Malicious proposal: The proposer may have uploaded different code than claimed
Error: WASM file not found

Run ./Deploy/docker-build.sh first to generate the WASM files.

First builds download all dependencies (~5-10 minutes). Subsequent builds use Docker cache (~2-3 minutes).


Q: Can I verify backend proposals without Docker?

No. Docker ensures the build environment is identical across all machines. Without it, Rust compiler differences could produce different binaries.

Q: Can I verify the Evidence Hash for frontend proposals?

Yes! Run dfx deploy ohshii_launcher_frontend --network ic --compute-evidence. This builds the frontend locally and computes the evidence hash without uploading anything. Compare it with the proposal’s Evidence field. This is the official DFINITY-recommended approach for asset canister verification (same process used by all SNS DAOs on the IC).

Q: Why does --compute-evidence need network access?

The evidence hash depends on the difference between the current canister state and the proposed new state. dfx must read the current asset canister state from the IC to compute what batch operations would be needed, then hash those operations.

Q: What if I disagree with the code changes?

That’s your prerogative as a voter! Verification confirms the WASM matches the code. Whether the code is good is a separate judgment call.

Q: Can the proposer fake the git commit hash?

Technically, they could reference a commit in a forked repository. Always verify the URL is the official ohshii-labs/ohshii-launcher. Always verify the hash of your own local build matches the hash shown in the proposal before voting.

Q: Why do proposals use .wasm.gz instead of raw .wasm?

Smaller file size reduces storage costs and upload time. The IC decompresses it automatically during installation.

Q: What’s TOCTOU protection?

Time-of-Check-Time-of-Use. It prevents tampering between when voters verify and when the code is executed. The backend recalculates hashes at execution time.

Q: How do I know the Dockerfile isn’t malicious?

Read it! It’s a few dozen lines. Look for standard Rust build commands. Report anything suspicious.



  • Clone the correct repository (ohshii-labs/ohshii-launcher)
  • Checkout the exact commit hash from the proposal
  • Read the Dockerfile before running it
  • Run ./Deploy/verify-proposal.sh <canister_name> (or ./Deploy/docker-build.sh + manual hash)
  • Compare your .wasm.gz hash with the proposal’s WASM Hash
  • MATCH = Safe to vote | MISMATCH = Do not vote
  • Clone the correct repository (ohshii-labs/ohshii-launcher)
  • Checkout the exact commit hash from the proposal
  • Run npm ci to install dependencies
  • Run dfx deploy ohshii_launcher_frontend --network ic --compute-evidence
  • Compare your computed evidence with the proposal’s Evidence field
  • MATCH = Safe to vote | MISMATCH = Investigate (may be Node.js version difference)

Remember: Trust but verify. Your vote protects the entire community.