Getting a Canister's Controller On-Chain

TL;DR
We want to allow a canister to query the status of another canister on mainnet, without being the controller. This will allow for an on-chain way of verifying that someone owns the canister they say they own.

The Current Way to Get a Canister’s Status / Controller

On Chain

You cannot make a call to whatever canister you’d like to, to get that canister’s list of controllers. The only way you can do this is in DFX, by running:

dfx canister --network=ic --no-wallet status <canister-id>

If you are the controller of the canister you will be returned with something that looks like this:

Canister status call result for <canister-id>.
Status: Running
Controller: <ID-of-controller>
Memory allocation: 0
Compute allocation: 0
Freezing threshold: 2_592_000
Memory Size: Nat(358829)
Balance: 4_899_635_664_822 Cycles
Module hash: <hash>

However, if you are not the controller you are returned a 403 Forbidden. This seems a bit redundant as the only way to get info on the controller is…. by being the controller :woman_facepalming:

Off-Chain

You can get the status / controller of any canister off-chain by using the state-tree of the IC. You make an HTTP call to the subnet and pass the desired canister ID along, and are returned the list of controllers for that canister.

Why This is an Issue

In a lot of projects, it is crucial to be able to verify that someone owns the canister that they say they own. It acts as a way of programmatically verifying that someone is who they say they are so that they can take some sort of action against that canister on an external service.

Without being able to verify that someone actually owns the canister they say they own, at least one party must rely on trust.

Isn’t there a workaround?

Yes, there are a couple of work-arounds but they all involve trust and leave the possibility of malicious actors.

  1. You can ask the canister to implement a method that could be called and would return the controller to the caller.

    Implementing a method that you have not written yourself involves trust because this method may not work how it is advertised to you.

  2. Verify that someone owns a canister by getting them to change the controller to your canister, with the promise of returning control back after verification.

    Again, the person who is getting validated is trusting the validator to return the canister exactly how they got it, or worse, return it at all!

Proposal

We propose that the management canister implements a way of making on-chain canister to canister query calls that returns the canister’s list of controllers. This could be exactly like the ‘canister_status’ method, but returning only the controllers and without restrictions.

This data is already available by using the method described in the “off-chain” subsection above.

Allowing for this would enable trustless verification that an actor owns the canister they claim to own.


We would also love to hear from the rest of the community on this topic as well. Maybe you’ve had the same issue, or maybe you’ve found a trustless way around this? We’d love to chat.

Thank you!

13 Likes

Same for the module_hash of the canister.

3 Likes

An alternative we discussed was simply making the canister status available to all. It’s restricted now simply to be on the safe side (is reading the cycle balance too much information?), but given the headache it causes, I’m swaying to simply allow anyone to use canister_status.

A separate public method is fine, too.

5 Likes

I say it is too much. A separate method we need.

1 Like

Personally, I don’t think that reading the cycle’s balance of a canister is too much information. However, maybe it would be best to leave that up to the community as a whole?

Additionally, how can we move this forward? It would be great to get this in-front of a wider portion of the community to get an answer to if we can just make ‘canister_status’ a public method, or if adding a separate method that only returns the list of controllers is the better route.

4 Likes

I would like to see this opened up as well. Currently developing an application where I need this info.

I think in general blockchains are not made for running private applications, but for running open composable services or at least those should be prioritised. This does not fit into that.

2 Likes

Another workaround at the moment is to add ic blackhole to the controller list, then anyone can query the blackhole canister to find out your canister status.

This is opt-in, instead of opt-out. Even if we choose to change the default to expose canister status, I’d still like to see an option to let controllers toggle it (then it would be opt-out).

7 Likes

NICE,Although I don’t understand it, I feel good. I hope I can continue to refuel.

1 Like

I am not sure I understand in detail what Sherlocked is trying to do. However it using dfx there is a simple way to find the controller (and the hash) of a running canister:
dfx canister --no-wallet info canister-id
This should allow to verify the owner of a canister. However, I do not know how to do this in Motoko. Implementing the info method in the management canister would be of high interest.

1 Like

This command works for me. I don’t think dfx interacts with the IC using the on-chain API. It should be using the off-chain API.

I am not seeing any major reason to not implement a separate method that allows this. This would be really nice to have.

2 Likes

Maybe canister_info, which will return the controller and module_hash is a good choice

2 Likes

I would also like to have a Motoko method for this. Useful for many different app types.

I don’t think that reading the cycle’s balance of a canister is too much information.

However, maybe it would be best to leave that up to the community as a whole?

Additionally, how can we move this forward? It would be great to get this in-front of a wider portion of the community to get an answer to if we can just make ‘canister_status’ a public method, or if adding a separate method that only returns the list of controllers is the better route.

2 Likes

Seems like there is no still no way to get the list of controllers on-chain (without using HTTP request) if you are not the owner of the canister.

Any reason for the management canister to not exposes a canister_info method enabling this use case? I don’t see, since you can do it off-chain anyway.

There’s actually a “hacky” way to do this (would be nice to have an officially supported way though)

Here’s the hacky way:

  1. From your canister, call the management canister’s canister_status API, providing the canister id of the canister you wish to retrieve controller information.
  2. If you control the canister, you’ll be able to easily see the controllers. However, since the majority of the time you will not be the controller of the canister in question, it will return an error. Wrap your call to canister_status in a try catch, as the error has a message saying that you are not the controller of that canister. However, in the error message, it will also provide the current controllers of the canister that you do not control!
  3. Parse this error message for the controllers, and voila! You have a different canister’s controllers.

Here’s a snippet of some code we wrote for CycleOps to do exactly this parsing of the error message - feel free to use!

  /// Parses the controllers from the error returned by canister status when the caller is not the controller
  /// Of the canister it is calling
  ///
  /// TODO: This is a temporary solution until the IC exposes this information.
  /// TODO: Note that this is a pretty fragile text parsing solution (check back in periodically for better solution)
  ///
  /// Example error message:
  ///
  /// "Only the controllers of the canister r7inp-6aaaa-aaaaa-aaabq-cai can control it.
  /// Canister's controllers: rwlgt-iiaaa-aaaaa-aaaaa-cai 7ynmh-argba-5k6vi-75frw-kfqpa-3xtca-nmzk3-hrmvb-fydxk-w4a4k-2ae
  /// Sender's ID: rkp4c-7iaaa-aaaaa-aaaca-cai"
  public func parseControllersFromCanisterStatusErrorIfCallerNotController(errorMessage : Text) : [Principal] {
    let lines = Iter.toArray(Text.split(errorMessage, #text("\n")));
    let words = Iter.toArray(Text.split(lines[1], #text(" ")));
    var i = 2;
    let controllers = Buffer.Buffer<Principal>(0);
    while (i < words.size()) {
      controllers.add(Principal.fromText(words[i]));
      i += 1;
    };
    Buffer.toArray<Principal>(controllers);
  };

Of course, this error message could be changed, so it is not a stable solution and is susceptible to trapping during execution if this specific error message were to be changed. For a more stable solution, I proposed Explicily expose IC specific error codes to canisters. Stable, more specific error codes, plus publicly exposing controller and wasm hash information through the management canister (similar to what’s exposed via HTTP request) would be the ideal scenario.

11 Likes

This isn’t as good as the solution above, but it looks like there’s a new ic0.is_controller API (that just got released in moc 0.9.1) Release 0.9.1 · dfinity/motoko · GitHub

Might be more stable than parsing the error message? :man_shrugging:

The problem with is_controller is that it only checks IF a principal is a controller, so you have to have a reasonable guess for which principals could be controllers

1 Like