プリンシパルと Caller の識別

Motoko の Shared 関数は、シンプルな Caller(関数の呼び出し元)の識別(Identification)をサポートしており、これにより関数の Caller に関連付けられた Internet Computer の プリンシパル を検査することが可能になります。 関数の呼び出しに関連づけられたプリンシパルは、ユニークなユーザか Canister スマートコントラクトを識別する値です。

関数の Caller に関連づけられたプリンシパルを用いて、プログラム内で基本的な形式の アクセスコントロール を実装することができます。

Motoko では、shared キーワードを用いて Shared 関数を宣言します。 また、Shared 関数は {caller : Principal} 型のオプション引数を宣言することができます。

Shared 関数の Caller にアクセスする方法を説明するため、以下のような関数を考えます:

shared(msg) func inc() : async () {
  // ... msg.caller ...
}

この例では、Shared 関数である inc() は Record 型である msg を受け取り、msg.callermsg のプリンシパルフィールドにアクセスします。

inc() 関数の呼び出しは変更されません。それぞれの関数呼び出しにおいて、呼び出し側のプリンシパルはユーザーではなくシステムから提供されます。そのため、悪意のあるユーザーはプリンシパルを偽造したり、なりすましたりすることができません。

Actor クラスのコンストラクタの Caller にアクセスするには、Actor クラスの宣言と同じ(オプショナルの)シンタックスを用います。 例えば、以下のようになります:

shared(msg) actor class Counter(init : Nat) {
  // ... msg.caller ...
}

この例を拡張し、Counter のインストーラであるプリンシパルだけが Counter を変更できるように制限したいとします。 これを行うには、Actor を設置したプリンシパルを owner 変数にバインドして記録します。 そうすることで、各メソッドの呼び出し元が owner と等しいかどうかを次のようにチェックすることができます:

shared(msg) actor class Counter(init : Nat) {

  let owner = msg.caller;

  var count = init;

  public shared(msg) func inc() : async () {
    assert (owner == msg.caller);
    count += 1;
  };

  public func read() : async Nat {
    count
  };

  public shared(msg) func bump() : async Nat {
    assert (owner == msg.caller);
    count := 1;
    count;
  };
}

この例では、assert (owner == msg.caller) により、関数 inc()bump() の呼び出しが認証されていなければトラップし、count 変数の変更を阻止します。一方、read() 関数はあらゆる呼び出しを許可しています。

また、shared の引数は単なるパターンなので、お好みで、上記をパターンマッチを使うように書き換えることもできます:

shared({caller = owner}) actor class Counter(init : Nat) {

  var count : Nat = init;

  public shared({caller}) func inc() : async () {
    assert (owner == caller);
    count += 1;
  };

  // ...
}
単純な Actor 宣言では、そのインストーラにアクセスすることはできません。Actor のインストーラにアクセスする必要がある場合は、Actor 宣言を引数なしの Actor クラスに書き換えてください。

プリンシパルは等価性、順序付け、ハッシングをサポートしているため、コンテナにプリンシパルを効率的に格納して、許可リストや拒否リストを管理することができます。 プリンシパルに関するその他の操作は、Principal 標準ライブラリを参照してください。