認証鍵の使用
⚠CAUTION
このサンプルは、本番環境では使用しないでください。これは、学習用として作成しており、テストの実施や本番環境仕様にする必要があります。
このチュートリアルでは、list_authorization_keys関数を用いたデプロイに紐づいている認証鍵の取得と利用方法についてお見せします。
let authorization_keys = runtime::list_authorization_keys();認証鍵は、署名者の署名と公開鍵、そして呼び出された認証鍵をリストアップしているDeployの approvals の項目にリストアップされていることを覚えておいてください。以下は、デプロイの承認(approval)についてのサンプルです。
"approvals": [
{
"signer": "02021a4da3d6f32ea3ebd2519e1a37a1b811671085bf4f1cf2a36b931344a99b756a",
"signature": "02df8cdf0bff3bd93e831d24563d5acbefa0ed13814550e910d03208d5fb3c11770dd3d918784ec84342e53666eacf59aeecbf4ce0cdd60e167c4a4b20e4b8f481"
}
]この例におけるコントラクトコードは、runtime::list_authorization_keys 関数を呼び出して得られたデプロイの認証鍵のセットを取得します。言い換えると、list_authorization_keys は、デプロイの署名を行った鍵を表すアカウントハッシュのセットを返します。インストールすると、コントラクトコードはNamedKeyにデプロイをインストールする認証鍵を格納します。コントラクトには、呼び出し元デプロイとインストールデプロイの認証鍵のインターセクションを返すエントリーポイントも含まれています。このリポジトリにあるtestsは、異なるシナリオの確認とインターセクションの結果の確認を行います。
前提条件
- 開発の事前準備を終え、Writing and testing on-chain code を使いこなせる状態である
- デプロイの送信方法と確認方法を把握している
- 下記項目について理解している
- Casper Accounts
- Deploys
- Associated Keys
- Approvals も認証鍵とされています
Workflow
開始するには、tutorials-example-wasmをクローンしてください。そして、authorization-keys-exampleディレクトリを開き、Rust 環境を準備してください。そして、以下のコマンドを用いてtestsをビルドします。
git clone https://github.com/casper-ecosystem/tutorials-example-wasm
cd tutorials-example-wasm/authorization-keys-example
make prepare
make testリポジトリ構造のレビュー:
- client – clientフォルダーには2つのWasmファイルが入っています。
add_keys.wasm– アソシエイトキーを呼び出しアカウントに追加するセッションコードcontract_call.wasm– コントラクトのエントリーポイントの呼び出しと名前付き鍵に結果を保存するセッションコード
- contract – 認証キーの使用方法のデモや
contract.wasmファイルへのコンパイルを行うシンプルなコントラクト - tests – Testsとコントラクトの想定された動作確認デモを行うサポートユーティリティ
💡NOTE
このチュートリアルでは、GitHubに記載しているコードのある行を取りあげています。
コントラクトのサンプル例
installationを行うと、このサンプルのコントラクトは名前付き鍵にインストールしたデプロイに署名を行った認証鍵を格納します。
#[no_mangle]
pub extern "C" fn init() {
if runtime::get_key(AUTHORIZATION_KEYS_INSTALLER).is_none() {
let authorization_keys: Vec<AccountHash> =
runtime::list_authorization_keys().iter().cloned().collect();
let authorization_keys: Key = storage::new_uref(authorization_keys).into();
runtime::put_key(AUTHORIZATION_KEYS_INSTALLER, authorization_keys);
}
}コントラクトには、呼び出し元のデプロイ用認証鍵とコントラクトのインストール中に保存されるインストーラーデプロイの認証鍵のインターセクションを返すエントリーポイントが含まれています。以下の runtime::list_authorization_keys の usage は、呼び出し用デプロイに署名した鍵を表示するアカウントハッシュのセットを取得します。
let authorization_keys_caller: Vec<AccountHash> =
runtime::list_authorization_keys().iter().cloned().collect();Client Wasmファイル
add_keys.wasm
このファイルには、呼び出しアカウントにアソシエイトキーを追加するセッションコードが含まれています。詳細についてや似たサンプルは、二社間によるデプロイへの複数署名(マルチシグ)チュートリアルをご確認ください。
contract_call.wasm
このセッションコードは、2つの鍵セット間のインターセクションを返すコントラクトのエントリーポイントを呼び出します。
- (このチュートリアルでは installer deploy と呼ばれている)コントラクトをインストールしたデプロイに署名した認証鍵
- (このチュートリアルでは caller deploy と呼ばれている)エントリーポイントを呼び出すデプロイに署名した認証鍵
インターセクション結果は、contract_call.wasmを呼び出すアカウントの名前付き鍵に格納されたリストです。
let key_name: String = runtime::get_named_arg(ARG_KEY_NAME);
let intersection =
runtime::call_contract::<Vec<AccountHash>>(contract_hash, ENTRY_POINT, runtime_args! {});
runtime::put_key(&key_name, storage::new_uref(intersection).into());
}サンプルのテスト
このセクションは、認証キーの使用方法のデモ用サンプルテストについてお伝えします。テストは3つのパートに分かれています。
- コントラクトのインストールのテスト
- コントラクトのユニークなエントリーポイントのテスト
- クライアントコントラクトの呼び出しを使用したエントリーポイントのテスト
これらのテストは、コントラクトのインストールに焦点を充てています。
Test 1: should_allow_install_contract_with_default_account
| Installer deploy authorization keys | Expected outcome |
|---|---|
DEFAULT_ACCOUNT_ADDR | Successful contract installation (コントラクトのインストールに成功) |
この test は、アカウントのアソシエイトキーに属する認証鍵である DEFAULT_ACCOUNT_ADDR による installer deploy への署名を行います。要するに、呼び出し元がデフォルトアカウントの為、DEFAULT_ACCOUNT_ADDR がデプロイへの署名に使われます。
let session_code = PathBuf::from(CONTRACT_WASM);
let session_args = RuntimeArgs::new();
let deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[*DEFAULT_ACCOUNT_ADDR])
.with_address(*DEFAULT_ACCOUNT_ADDR)
.with_session_code(session_code, session_args)
.build();Test 2: should_disallow_install_with_non_added_authorization_key
| Installer deploy authorization keys | Expected outcome |
|---|---|
DEFAULT_ACCOUNT_ADDR, account_addr_1 | Failed contract installation (コントラクトのインストールに失敗) |
この test では、呼び出し元のアソシエイトキーの一部ではない認証鍵による installer deploy への署名を試みます。それは、デプロイへの署名に使った認証鍵は、呼び出し元のアソシエイトキーのサブセットである必要がある為です。よって、デプロイのインストールは想定通り失敗します。
let session_code = PathBuf::from(CONTRACT_WASM);
let session_args = RuntimeArgs::new();
let deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[*DEFAULT_ACCOUNT_ADDR, account_addr_1])
.with_address(*DEFAULT_ACCOUNT_ADDR)
.with_session_code(session_code, session_args)
.build();
let execute_request = ExecuteRequestBuilder::from_deploy_item(deploy_item).build();
builder.exec(execute_request).commit().expect_failure();
let error = builder.get_error().expect("must have error");
assert_eq!(error.to_string(), "Authorization failure: not authorized.");Test 3: should_allow_install_with_added_authorization_key
| Installer deploy authorization keys | Expected outcome |
|---|---|
DEFAULT_ACCOUNT_ADDR, account_addr_1 | Successful contract installation (コントラクトのインストールに成功) |
この test では、追加された認証鍵を用いてデプロイのインストールを成功させるデモを行います。テストフレームワークの初期設定後、テストがセッションコードを呼び出し、アソシエイトアカウントである account_addr_1 とデフォルトアカウントのアソシエイトキーに追加します。
// Add account_addr_1 to the default account's associated keys
let session_code = PathBuf::from(ADD_KEYS_WASM);
let session_args = runtime_args! {
ASSOCIATED_ACCOUNT => account_addr_1
};
let add_keys_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[*DEFAULT_ACCOUNT_ADDR])
.with_address(*DEFAULT_ACCOUNT_ADDR)
.with_session_code(session_code, session_args)
.build();
let add_keys_execute_request =
ExecuteRequestBuilder::from_deploy_item(add_keys_deploy_item).build();
builder
.exec(add_keys_execute_request)
.commit()
.expect_success();デプロイの閾値が現在2に設定されている為、installer deployはデフォルトのアカウントハッシュと account_addr_1 によって署名されます。GitHubをご確認ください。
let session_code = PathBuf::from(CONTRACT_WASM);
let deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[*DEFAULT_ACCOUNT_ADDR, account_addr_1])
.with_address(*DEFAULT_ACCOUNT_ADDR)
.with_session_code(session_code, session_args)
.build();
let execute_request = ExecuteRequestBuilder::from_deploy_item(deploy_item).build();
builder.exec(execute_request).commit().expect_success();次のテストでは、呼び出し元デプロイの認証鍵とインストールデプロイの認証鍵間のインターセクションを計算するコントラクトのユニークエントリポイントの実行を行います。
Test 4: should_allow_entry_point_with_installer_authorization_key
| Installer deploy authorization keys | Caller deploy authorization keys | Intersection returned by the entry point |
|---|---|---|
DEFAULT_ACCOUNT_ADDR | account_addr_1, DEFAULT_ACCOUNT_ADDR | account_addr_1 |
この test は、デフォルトアカウントのアソシエイトキーにアソシエイトアカウントを追加し、その2つの鍵を使ってコントラクトをインストールした前回のテスト上にビルドします。更には、201行目で test は account_addr_1 で署名した ACCOUNT_USER_1 にて実行するデプロイを用いたコントラクトのエントリーポイントを呼び出します。ACCOUNT_USER_1 のデプロイアクション閾値がここに設定されている通り1である為、これが可能なのです。
let contract_hash = builder
.get_expected_account(*DEFAULT_ACCOUNT_ADDR)
.named_keys()
.get(CONTRACT_HASH)
.expect("must have this entry in named keys")
.into_hash()
.map(ContractHash::new)
.unwrap();
let entry_point_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[account_addr_1])
.with_address(account_addr_1)
.with_stored_session_hash(contract_hash, ENTRYPOINT, runtime_args! {})
.build();
let entry_point_request =
ExecuteRequestBuilder::from_deploy_item(entry_point_deploy_item).build();
builder.exec(entry_point_request).expect_success().commit();エントリーポイントは、呼び出し元デプロイの認証鍵とインストールデプロイの認証鍵のインターセクションを返します。インターセクションは、account_addr_1鍵が含まれたリストです。よって、呼び出し元デプロイは、成功する想定の上で結果を返します。
Test 5: should_allow_entry_point_with_account_authorization_key
| Installer deploy authorization keys | Caller deploy authorization keys | Intersection returned by the entry point |
|---|---|---|
DEFAULT_ACCOUNT_ADDR | account_addr_1, DEFAULT_ACCOUNT_ADDR | DEFAULT_ACCOUNT_ADDR |
このサンプルリポジトリの main test となります。デフォルトアカウントを使ってコントラクトをインストール後、test は、アソシエイトキーとしてデフォルトアカウントハッシュを ACCOUNT_USER_1 に追加します。
let session_code = PathBuf::from(ADD_KEYS_WASM);
let session_args = runtime_args! {
ASSOCIATED_ACCOUNT => *DEFAULT_ACCOUNT_ADDR
};
let add_keys_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[account_addr_1])
.with_address(account_addr_1)
.with_session_code(session_code, session_args)
.build();そして、test がデプロイを作成しコントラクトのエントリーポイントを呼び出します。このデプロイは、ACCOUNT_USER_1 にて実行され、account_addr_1とデフォルトアカウントハッシュの2つの認証鍵を持っています。デプロイのアクション閾値は2に設定されている為、それを満たすには認証鍵2つ共がデプロイに署名する必要があります。結果のインターセクションはデフォルトアカウントハッシュを持っているので、デプロイ実行は成功するはずです。
let entry_point_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[account_addr_1, *DEFAULT_ACCOUNT_ADDR])
.with_address(account_addr_1)
.with_stored_session_hash(contract_hash, ENTRYPOINT, runtime_args! {})
.build();
let entry_point_request =
ExecuteRequestBuilder::from_deploy_item(entry_point_deploy_item).build();
builder.exec(entry_point_request).expect_success().commit();Test 6: should_disallow_entry_point_without_authorization_key
| Installer deploy authorization keys | Caller deploy authorization keys | Intersection returned by the entry point |
|---|---|---|
DEFAULT_ACCOUNT_ADDR | account_addr_2 | None |
この test は、呼び出し元デプロイの認証鍵とインストールデプロイの認証鍵の間のインターセクションが存在しない場合は、エントリーポイントがエラーを返すことを確認します。
デフォルトのアカウントハッシュは、installer deployへの署名に使用されます。
let session_code = PathBuf::from(CONTRACT_WASM);
let deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[*DEFAULT_ACCOUNT_ADDR])
.with_address(*DEFAULT_ACCOUNT_ADDR)
.with_session_code(session_code, runtime_args! {})
.build();test には、新規アカウントである ACCOUNT_USER_2 がコントラクトのエントリーポイントを呼び出すデプロイを作成し、account_addr_2 を用いてデプロイに署名します。エントリーポイントを呼び出すと、エラーが返されます。なぜなら、呼び出し元とinstaller deployが共通の認証鍵を持っていないからです。
// Here ACCOUNT_USER_2 does not have DEFAULT_ACCOUNT_ADDR (from the contract installer) in its associated keys
// The deploy will therefore revert with PermissionDenied
let entry_point_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[account_addr_2])
.with_address(account_addr_2)
.with_stored_session_hash(contract_hash, ENTRYPOINT, runtime_args! {})
.build();
let entry_point_request =
ExecuteRequestBuilder::from_deploy_item(entry_point_deploy_item).build();
builder.exec(entry_point_request).commit().expect_failure();
let error = builder.get_error().expect("must have User error: 0");
assert_expected_error(
error,
0,
"should fail execution since DEFAULT_ACCOUNT_ADDR is not in ACCOUNT_USER_2 associated keys",
);以下テストでは、contract call を用いたエントリーポイントの実行練習と返された結果の確認を行います。
Test 7: should_allow_entry_point_through_contract_call_with_authorization_key
| Installer deploy authorization keys | Caller deploy authorization keys | Intersection returned by the entry point |
|---|---|---|
DEFAULT_ACCOUNT_ADDR | account_addr_1, DEFAULT_ACCOUNT_ADDR | DEFAULT_ACCOUNT_ADDR |
この test は、クライアントによるコントラクトの呼び出しにて、コントラクトのエントリーポイントを検証します。コントラクトは、デフォルトのアカウントハッシュにてデプロイの認証鍵にインストールされます。
let session_code = PathBuf::from(CONTRACT_WASM);
let deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[*DEFAULT_ACCOUNT_ADDR])
.with_address(*DEFAULT_ACCOUNT_ADDR)
.with_session_code(session_code, runtime_args! {})
.build();呼び出し元のデプロイ(caller deploy)は、account_addr_1 and DEFAULT_ACCOUNT_ADDR によって署名されます。
let entry_point_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[account_addr_1, *DEFAULT_ACCOUNT_ADDR])
.with_address(account_addr_1)
.with_session_code(session_code, session_args)
.build();
let entry_point_request =
ExecuteRequestBuilder::from_deploy_item(entry_point_deploy_item).build();
builder.exec(entry_point_request).expect_success().commit();そこで、testは、返された結果がデフォルトのアカウントハッシュを持つ ACCOUNT_USER_1の名前付き鍵に保存されたことを確認します。
let intersection_receipt: Key = *builder
.get_expected_account(account_addr_1)
.named_keys()
.get(INTERSECTION_RECEIPT)
.expect("must have this entry in named keys");
let actual_intersection = builder
.query(None, intersection_receipt, &[])
.expect("must have stored_value")
.as_cl_value()
.map(|intersection_cl_value| {
CLValue::into_t::<Vec<AccountHash>>(intersection_cl_value.clone())
})
.unwrap()
.unwrap();
let expected_intersection = vec![*DEFAULT_ACCOUNT_ADDR];
assert_eq!(actual_intersection, expected_intersection);Test 8: should_disallow_entry_point_through_contract_call_without_authorization_key
| Installer deploy authorization keys | Caller deploy authorization keys | Intersection returned by the entry point |
|---|---|---|
DEFAULT_ACCOUNT_ADDR | account_addr_1, account_addr_2 | None |
このチュートリアルの final test が呼び出し元デプロイの認証鍵(account_addr_1, account_addr_2)とインストーラデプロイの認証鍵(DEFAULT_ACCOUNT_ADDR)間にインターセクションが存在しないことを確認した場合は、エントリーポイントがエラーを返します。
let session_code = PathBuf::from(CONTRACT_CALL_WASM);
let session_args = runtime_args! {
ARG_CONTRACT_HASH => Key::from(contract_hash),
ARG_KEY_NAME => INTERSECTION_RECEIPT
};
// account_addr_2 as an associated key is not among the default account's associated keys
// The deploy will therefore revert with PermissionDenied
let entry_point_deploy_item = DeployItemBuilder::new()
.with_empty_payment_bytes(runtime_args! {ARG_AMOUNT => *DEFAULT_PAYMENT})
.with_authorization_keys(&[account_addr_1, account_addr_2])
.with_address(account_addr_1)
.with_session_code(session_code, session_args)
.build();
let entry_point_request =
ExecuteRequestBuilder::from_deploy_item(entry_point_deploy_item).build();
builder.exec(entry_point_request).commit().expect_failure();
let error = builder.get_error().expect("must have User error: 0");
assert_expected_error(
error,
0,
"should fail execution since ACCOUNT_USER_2 as associated key is not in installer (DEFAULT_ACCOUNT_ADDR) associated keys",
);