Let's discuss reproducible builds and code verification once again

Hi everyone, I’m having a few related questions that we’re currently thinking about; what’s the recommended best practice for verifying a canister’s wasm programmatically? Is it calling the canister_status endpoint of the IC management canister and checking the module hash (as described here: Canister settings | Internet Computer)?

Is there a way to see who upgraded the canister in the past (like a log of upgrades)? And how could we ensure that only certain controllers are actually allowed to upgrade the canister code? Are there existing mechanisms for these?

aaaaa-aa:canister_info provides the current hash plus past versions

1 Like

By “packages” here in this sentence you are talking about system packages (as in .deb packages), right? As opposed to mops packages I mean (or crates for Rust).

1 Like

Yes, you can see which principal made an upgrade. The history returned by canister_info from the management canister (same as in @Severin’s answer above) contains this information. But:

  • the call is not available from outside the IC, only to canisters
  • it only has 20 history entries, not all

Here’s a frontend that does the call for you and that can track more than 20 entires of history (if you ask it to track a canister id): https://history.ic0.info/

2 Likes

Yes! Rust packages are pinned with Cargo.lock file, and any update is easy to identify.

base developer confirmed

1 Like

canister_status is only available to controllers of the canister. I believe you want to programmatically get the module hash of any third-party canister. The options are:

  1. With dfx: dfx canister --ic info <canister id>

  2. From your own code running outside the IC: You have to read from the state tree which is a bit inconvenient. See this forum thread where I asked the same question: How does dfx call canister_info and canister_status?
    @Severin gave some links to code examples there (Rust and js). You can also see how the frontend on https://history.ic0.info does it here: useReadState function

  3. From canister code: As @Severin mentioned above, call the management canister function canister_info. But it only works from canister code, not from outside the IC.

1 Like

Why --force?

I don’t know how to make building the assets reproducible. But I looked once into how comparing locally built assets against the assets in an asset canister. And the easiest way I found is to use the list method of an asset canister, which lists all files with their sha256 hashes, and then compare each file one by one. This approach seemed easier than to build a Merkle tree of hashes of files and then compare it to a root hash in the state tree.

Right, only --no-name is needed. I’ve updated my post above—good catch!

It’s what I do too.

Ok, please do this approach and let’s try it out. That’s also why I made my template repo, as a proposal to try it out. But please make sure the dependency on cpu architecture is taken care of (which nix alone does not).

I think this discussion has brought forward two opposing philosophies, both valid, and both should be tried out in practice.

Philosophy 1:

  • pin as much as possible, in particular all packages
  • argument that pinning everything is better for long-term verifiability
  • argument that pinning everything is better against supply-chain attacks
  • developer environment = validator environment

Philosophy 2:

  • analyse in detail what impacts build and what doesn’t
  • use the information gained, pin as little as possible
  • argument that pinning as little as possible is better for long-term verifiability
  • argument that pinning as little as possible is better against supply-chain attacks
  • developer environment != validator environment
2 Likes

Awesome, thank you @timo and @Severin !

Have you seen any best practice how to limit which controllers are allowed to upgrade the canister and which aren’t allowed to? We want to set this such that only one of the controllers can run the update (and thus change the code) and no one else.

did you hear about Orbit already? this is actually one possible solution to achieve that. the public beta has been released last week.

1 Like

What are the other controllers for that are not upgrading the code?

We want to keep at least one other canister and the user as controllers but want to make sure that only one dedicated canister is the controller who can run code updates. Would adding a simple check in the preupgrade hook be a good idea or could this be achieved more elegantly?

And what should these other controllers be allowed to do?

That’s an interesting question if the preupgrade function has access to a “caller” and if so then who that caller is. Don’t know myself.

We use it as a whitelisting mechanism such that only controllers can call certain canister endpoints and to make common controller calls, e.g. to get canister info like the cycle balance.

True, having access to the caller in preupgrade is the prerequisite for that idea to work, I had just assumed it has. I’ll look into this :+1:

This could be also a principal allow list guard within the canister method implementation right?

See allowed viewers in: NNS Proposal: Add Public and Restricted canister_status Visibility. To my understanding, this isn’t available yet but planned (correct me if I’m wrong).

Overall, I would avoid assigning principals as controllers if they shouldn’t have full access to the canister (e.g. perform upgrades). For example, controllers can also stop and wipe canisters, which I assume isn’t something you’d want to give to controllers that only need information like cycles.

1 Like

https://nuance.xyz/force/13232-434go-diaaa-aaaaf-qakwq-cai/a-vision-for-software-building-and-distribution-based-on-the-world-computer

I published an article about code verification on nuance a few minutes ago. Feel free to discuss it with me.

1 Like

Then should access-control the endpoints in canister code, like @sea-snake said, by maintaining an allow list in canister code, not rely on who is an IC-level controller.

For checking cycle balance the usual way is to add a black hole canister who reads the cycle balance as a proxy.

1 Like

I post a new topic on Dfinity forum A Vision for Software Building and Distribution Based on the Internet Computer.

My point is that even if we perfectly standardize reproducible builds, the problems still exist:
Not many people attempt reproduction or code verification.

For example, in the past, we’ve seen numerous NNS proposals related to infrastructure upgrades where most lacked this kind of verification. Most people had no idea who performed the verification or what the results were, leaving them to vote blindly.

Even for the most critical IC infrastructure, this is already the case—let alone for other dApps, where the situation is even worse.