Skip to content

Latest commit

 

History

History
137 lines (107 loc) · 6.63 KB

VERIFY.md

File metadata and controls

137 lines (107 loc) · 6.63 KB

Verify it yourself

The gateway is live in simulation mode on mainnet: https://cyclepay.raymondk.co, frontend shy4u-4qaaa-aaaay-aadhq-cai, backend saz2a-riaaa-aaaay-aadha-cai.

"Is the page I am using built from this repo?" — yes, check it

The canister publishes a state hash: one SHA-256 over every asset it serves, each one's bytes in every encoding, its content_type and its response headers, plus the redirect rules in match order. Compute the same number from your own build and compare.

You need icp, Node, and a Rust toolchain: the hash is defined by the certified-assets project's own preparation code, so computing it means building that project's verifier once from source.

git clone --recurse-submodules https://github.com/marc0olo/cyclepay && cd cyclepay
git checkout vX.Y.Z                             # the release the deployment published
npm --prefix src/frontend ci && npm --prefix src/frontend run build
scripts/check-frontend-hash.py -e ic

That script reads the pinned certified-assets release out of icp.yaml, builds the matching verifier, refuses to compare unless the canister reports running that release, and diffs the two numbers. To run the steps yourself instead:

cargo install --git https://github.com/dfinity/certified-assets \
  --tag v0.3.3 --locked state-hash-cli        # the release icp.yaml pins
state-hash src/frontend/dist
icp canister call shy4u-4qaaa-aaaay-aadhq-cai state_hash '()' -n ic -o hex | tail -c 65
  • ⚠️ Check out the tag, not main. main moves on after a release, and one changed HTML comment is enough to change the hash.
  • ⚠️ Build the verifier with --locked, never download one. Without --locked, cargo install re-resolves dependencies, and a newer brotli patch emits different bytes for the same input.
  • ⚠️ Do not check the frontend's module hash instead. The @dfinity/static-site recipe installs a pre-built certified-assets wasm, so that hash describes the recipe, not the page.

"Is the backend module built from this repo?" — compare it to a release

Every release publishes the module hashes its container build produced. Rebuild the tag yourself and compare your build, the release notes, and the canister:

git checkout vX.Y.Z
scripts/reproducible-build.sh vX.Y.Z              # writes release/MODULE-HASHES.txt
icp canister status saz2a-riaaa-aaaay-aadha-cai -n ic -p
gh release view vX.Y.Z                            # the published hashes

All three must agree, including the # build arch: line: the same commit produces different bytes on different platforms. RELEASE.md is the procedure that keeps them in step.

Status: the running module is v0.1.0-beta.1's published build. It was installed from the container artifact whose hashes that release publishes, and the install gated on the canister reporting them. No hash is restated here; the commands read the live ones.

What anyone can check right now

No identity, no permissions, nothing installed but icp.

The price you are shown is produced by the code that locks it. quote_previews is a public query running the same pricing path as order creation, and returns both rate inputs so the arithmetic is yours to redo:

icp canister call backend quote_previews '(vec { 1_000 : nat })' -e ic

Pricing crosses no trust boundary. Both rates come from canisters, the Exchange Rate Canister for USD/ICP and the CMC for XDR/ICP, read on a timer. There are exactly three HTTPS outcalls in the system and all three go to Stripe (create, expire and retrieve a Checkout Session). See docs/ARCHITECTURE.md.

The operational state is public, so solvency is checkable against the cycles ledger without this canister's cooperation:

icp canister call backend reserve_status   '()' -e ic   # floor, promised, available
icp canister call backend pricing_status   '()' -e ic   # both rates, the divisor
icp canister call backend lifecycle_config '()' -e ic   # the gate's bounds
icp canister call backend cycles_status    '()' -e ic
icp canister call backend recovery_status  '()' -e ic
icp canister call backend health           '()' -e ic

The running canister names its own commit. The recipe embeds the committed src/backend/dist/backend.did as the canister's candid:service metadata, so you can find which tree it was built from without any published hash:

icp canister metadata backend candid:service -e ic > /tmp/deployed.did
git show <ref>:src/backend/dist/backend.did | diff - /tmp/deployed.did

An exact match (bar one trailing newline) means the deployed interface is that ref's. It narrows the commit rather than proving the bytes, since commits that do not touch the interface share it.

The reserve floor is enforced by an omission in a type. src/backend/Delivery.mo declares the cycles-ledger interface the canister may call, and icrc2_approve and the ledger's withdraw are absent, so they cannot be called. scripts/test-all.sh fails on a declaration that widens it.

Frontend responses are certified per response: each carries IC-Certificate over the asset tree and the gateway rejects a response whose certificate does not verify.

Every suite is in the repo and one command runs them all: scripts/test-all.sh, with docs/TEST-COVERAGE.md stating what is not covered and why.

What a buyer can check that a visitor cannot

receipt(orderId) is owner-scoped (caller == order.owner). It returns both rate inputs and the delivery block index, so a buyer can recompute their own price and confirm the transfer on the cycles ledger independently.

The limits, in the same breath

  • The published hash is reproducible on one architecture. The build is pinned to --platform linux/amd64 and the release records it. Rebuilding elsewhere gives different bytes and proves nothing.
  • The hashes are published by us. Anyone can produce the same bytes, but nobody else counter-signs them.
  • Any single controller can upgrade and drain. IC controllers are OR-semantics; the hardening path is a multisig canister as sole controller.
  • The webhook secret is plaintext canister state, protected by the confidential subnet rather than by cryptography. HMAC is symmetric, so a canister that can verify can also forge. Checkpoint and state-sync confidentiality are confirmed on the target subnet; attestation coverage of every replica is not. RUNBOOK.md's confidential-subnet checklist carries it.
  • A purchase requires an allow-listed principal while test payments are accepted, so a visitor cannot exercise the buying path end to end on the live gateway.