2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Github CopilotでSolidityのコードをICP上のRustに変換する

Posted at

はじめに

SolidityというEVM系チェーンで使われるスマートコントラクト用のソースコードをICP上で動くRustのコードに変換してみます。

verify-media/contracts

今回の変換対象は、FoxのVerifyというフェイクニュース対策用のスマートコントラクトです。

まず、下記のコマンドでgit cloneします。

git clone https://github.com/verify-media/contracts

contractsフォルダをVS CodeのFile -> Open Folderで開きます。

IdentityRegistry.sol

最初の変換対象はIdentityRegistry.solです。

このファアイルを開きます。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {EIP712Upgradeable} from "@openzeppelin-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol";

/**
 * @title IdenityRegistry
 * @author Blockchain Creative Labs
 * @notice A registry of named root signing key pairs and their intermediate keypairs.
 */
contract IdentityRegistry is OwnableUpgradeable, EIP712Upgradeable {
    mapping(address => bool) public registered;
    mapping(address => string) public rootName;
    mapping(string => address) public nameToRoot;
    mapping(address => uint256) public nonces;
    mapping(address => bool) public used;

    mapping(address => address) intermediateToRoot;
    mapping(address => mapping(address => uint256)) registryExpiry;

    error NotRegistered();
    error InvalidSignature();
    error InvalidParams();
    error RegistryExpired();
    error SignatureExpired();
    error AlreadyRegistered();

    modifier onlyRegistered(address user) {
        if (!registered[user]) {
            revert NotRegistered();
        }
        _;
    }

    bytes32 emptyString = keccak256(abi.encode(""));

    /**
     * @dev Used to initialize the values through the transparent proxy upgrade pattern.
     * https://docs.openzeppelin.com/upgrades-plugins/1.x/proxies
     */
    function initialize(string memory name_, string memory version_) public initializer {
        __Ownable_init(msg.sender);
        __EIP712_init(name_, version_);
    }

    /**
     * @notice Registers a given root address to a user facing name, only callable from the protocol wallet
     * @param root The address to register.
     * @param name The user facing name of the address, Org or real world entity.
     */
    function registerRoot(address root, string memory name) external onlyOwner {
        if (root == address(0) || (keccak256(abi.encode(name)) == emptyString)) revert InvalidParams();
        if (used[root] || (nameToRoot[name] != address(0))) revert AlreadyRegistered();
        used[root] = true;
        registered[root] = true;
        rootName[root] = name;
        nameToRoot[name] = root;
    }

    /**
     * @notice Unregisters an address from the registry. Only Callable from the protocol wallet.
     * @param root The address to unregister.
     */
    function unregisterRoot(address root) external onlyOwner {
        nameToRoot[rootName[root]] = address(0);
        registered[root] = false;
        rootName[root] = "";
    }

    /**
     * @notice Registers an address as a intermediate identity of a registered root identity using a signature from the root identity keypair.
     * Will only register intermediate identities that have not been registered before. Can be used to extend a existing registry during only
     * while the existing signature has not expired.
     * @param signature The signature from the root identity.
     * @param root the address of the root identity.
     * @param intermediate the address of the intermediate identity to register
     * @param expiry the uint256 timestamp of the expiry of the intermediate identity acting on behalf of the root.
     * @param chainID the id of the chain which this signature is for, used to avoid replay signatures between testnet/mainnet enviorments.
     * @param deadline the uint256 timestamp of the deadline to use the signature by.
     */
    function register(
        bytes memory signature,
        address root,
        address intermediate,
        uint256 expiry,
        uint256 chainID,
        uint256 deadline
    ) external onlyRegistered(root) {
        if (block.timestamp > deadline) revert SignatureExpired();
        if (used[intermediate]) {
            if (intermediateToRoot[intermediate] != root) revert AlreadyRegistered();
            if (block.timestamp > registryExpiry[root][intermediate]) revert RegistryExpired();
        }
        if (chainID != block.chainid) revert InvalidParams();
        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(
                    keccak256(
                        "register(address root,address intermediate,uint256 expiry,uint256 nonce,uint256 chainID,uint256 deadline)"
                    ),
                    root,
                    intermediate,
                    expiry,
                    nonces[root],
                    chainID,
                    deadline
                )
            )
        );
        address signer = ECDSA.recover(digest, signature);
        if (signer != root) revert InvalidSignature();
        ++nonces[root];
        _register(root, intermediate, expiry);
    }

    /**
     * @dev Internal function which stores the signature and registers the intermediate identity address to the root identity address.
     */
    function _register(address root, address intermediate, uint256 expiry) internal {
        used[intermediate] = true;
        registryExpiry[root][intermediate] = expiry;
        intermediateToRoot[intermediate] = root;
    }

    /**
     * @notice Unregisters an intermediate identity address from a root identity using a signature from the root identity.
     * @param signature The signature from the root identity.
     * @param root the address of the root identity.
     * @param intermediate the address of the intermediate identity to unregister.
     * @param chainID the id of the chain which this signature is for, used to avoid replay signatures between testnet/mainnet enviorments.
     * @param deadline the uint256 timestamp of the deadline to use the signature by.
     */
    function unregister(bytes memory signature, address root, address intermediate, uint256 chainID, uint256 deadline)
        external
        onlyRegistered(root)
    {
        if (intermediateToRoot[intermediate] != root) revert InvalidParams();
        if (block.timestamp > deadline) revert SignatureExpired();
        if (chainID != block.chainid) revert InvalidParams();
        bytes32 digest = _hashTypedDataV4(
            keccak256(
                abi.encode(
                    keccak256(
                        "unregister(address root,address intermediate,uint256 nonce,uint256 chainID,uint256 deadline)"
                    ),
                    root,
                    intermediate,
                    nonces[root],
                    chainID,
                    deadline
                )
            )
        );
        address signer = ECDSA.recover(digest, signature);
        if (signer != root) revert InvalidSignature();
        ++nonces[root];
        _unregister(root, intermediate);
    }

    /**
     * @dev Internal function to expire a signature to end association between intermediate identity address and root identity.
     */
    function _unregister(address root, address intermediate) internal {
        registryExpiry[root][intermediate] = 0;
    }

    /**
     * @notice Returns the corresponding root identity of an intermediate identity while the stored signature is valid. Address(0) means no association exists
     * @param identity The address of an intermediate identity to lookup.
     */
    function whoIs(address identity) external view returns (address root) {
        root = intermediateToRoot[identity];
        if (registered[root]) {
            if (block.timestamp > registryExpiry[root][identity]) root = address(0);
        } else {
            return address(0);
        }
    }
}

コマンド(CTL)+iでインラインチャットを開いて、ICPで動くRustに変換してと入力します。

下記のようなコードが作られたので、コピーして、Rustのソースコードに貼り付けます。

use ic_cdk::export::candid::{CandidType, Deserialize};
use ic_cdk::export::Principal;
use ic_cdk_macros::*;
use std::collections::BTreeMap;

#[derive(Clone, CandidType, Deserialize)]
struct IdentityRegistry {
    registered: BTreeMap<Principal, bool>,
    root_name: BTreeMap<Principal, String>,
    name_to_root: BTreeMap<String, Principal>,
    nonces: BTreeMap<Principal, u64>,
    used: BTreeMap<Principal, bool>,
    intermediate_to_root: BTreeMap<Principal, Principal>,
    registry_expiry: BTreeMap<Principal, BTreeMap<Principal, u64>>,
}

#[init]
fn init() -> IdentityRegistry {
    IdentityRegistry {
        registered: BTreeMap::new(),
        root_name: BTreeMap::new(),
        name_to_root: BTreeMap::new(),
        nonces: BTreeMap::new(),
        used: BTreeMap::new(),
        intermediate_to_root: BTreeMap::new(),
        registry_expiry: BTreeMap::new(),
    }
}

#[update]
fn register_root(&mut self, root: Principal, name: String) {
    if root == Principal::anonymous() || name.is_empty() {
        panic!("Invalid parameters");
    }
    if self.used.contains_key(&root) || self.name_to_root.contains_key(&name) {
        panic!("Already registered");
    }
    self.used.insert(root.clone(), true);
    self.registered.insert(root.clone(), true);
    self.root_name.insert(root.clone(), name.clone());
    self.name_to_root.insert(name.clone(), root.clone());
}

#[update]
fn unregister_root(&mut self, root: Principal) {
    if let Some(name) = self.root_name.get(&root) {
        self.name_to_root.remove(name);
    }
    self.registered.insert(root.clone(), false);
    self.root_name.insert(root.clone(), "".to_string());
}

#[update]
fn register(
    &mut self,
    signature: Vec<u8>,
    root: Principal,
    intermediate: Principal,
    expiry: u64,
    chain_id: u64,
    deadline: u64,
) {
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if self.used.contains_key(&intermediate) {
        if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
            if root_identity != &root {
                panic!("Already registered");
            }
            if ic_cdk::api::time() > self.registry_expiry[root_identity][&intermediate] {
                panic!("Registry expired");
            }
        }
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&expiry.to_le_bytes());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._register(root, intermediate, expiry);
}

#[update]
fn unregister(&mut self, signature: Vec<u8>, root: Principal, intermediate: Principal, chain_id: u64, deadline: u64) {
    if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
        if root_identity != &root {
            panic!("Invalid parameters");
        }
    }
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._unregister(root, intermediate);
}

#[query]
fn who_is(&self, identity: Principal) -> Option<Principal> {
    if let Some(root) = self.intermediate_to_root.get(&identity) {
        if let Some(expiry_map) = self.registry_expiry.get(root) {
            if ic_cdk::api::time() > expiry_map[&identity] {
                return None;
            }
        }
        return Some(root.clone());
    }
    None
}

impl IdentityRegistry {
    fn _register(&mut self, root: Principal, intermediate: Principal, expiry: u64) {
        self.used.insert(intermediate.clone(), true);
        self.registry_expiry
            .entry(root.clone())
            .or_insert_with(BTreeMap::new)
            .insert(intermediate.clone(), expiry);
        self.intermediate_to_root.insert(intermediate, root);
    }

    fn _unregister(&mut self, root: Principal, intermediate: Principal) {
        self.registry_expiry
            .entry(root.clone())
            .and_modify(|expiry_map| {
                expiry_map.remove(&intermediate);
            });
    }
}

ステート変数

Solidityのcontractのステート変数は、Stable structuresに格納します。

Copilotが下記のようにSolidityのcontractをRustのstructに変換していますが、

#[derive(Clone, CandidType, Deserialize)]
struct IdentityRegistry {
    registered: BTreeMap<Principal, bool>,
    root_name: BTreeMap<Principal, String>,
    name_to_root: BTreeMap<String, Principal>,
    nonces: BTreeMap<Principal, u64>,
    used: BTreeMap<Principal, bool>,
    intermediate_to_root: BTreeMap<Principal, Principal>,
    registry_expiry: BTreeMap<Principal, BTreeMap<Principal, u64>>,
}

これをThreadLocalを使ったStable Structuresを使うように変更します。
先に、ic-stable-structuresクレートを追加しておきましょう。

cargo add ic-stable-structures

一旦下記のようにstructthread_local!に変更します。

thread_local! {
    registered: BTreeMap<Principal, bool>,
    root_name: BTreeMap<Principal, String>,
    name_to_root: BTreeMap<String, Principal>,
    nonces: BTreeMap<Principal, u64>,
    used: BTreeMap<Principal, bool>,
    intermediate_to_root: BTreeMap<Principal, Principal>,
    registry_expiry: BTreeMap<Principal, BTreeMap<Principal, u64>>,
}

root_namename_to_rootnonces, usedは、コードを見る限り必要ないので、削除します。

registeredintermediate_to_root, registry_expiryをStableBTreeMapを使うように書きえます。

use std::cell::RefCell;

use candid::Principal;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};

thread_local! {
    static MEM_MGR: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static REGISTERED: RefCell<StableBTreeMap<Principal, bool, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(0))))
    );

    static INTERMEDIATE_TO_ROOT: RefCell<StableBTreeMap<Principal, Principal, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(1))))
    );

    static REGISTRY_EXPIRY: RefCell<StableBTreeMap<(Principal, Principal), u64, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(2))))
    );
}

init()は必要ないので消しておきます。

rootとintermediate

このシステムにおけるrootとは、記事を登録する会社です。rootは、このCanisterの所有者が登録します。

intermediateは、実際に記事を登録する編集者です。intermediateは、rootが登録します。

register_root

Github Copilotregister_root()を下記のように変換してました。

#[update]
fn register_root(&mut self, root: Principal, name: String) {
    if root == Principal::anonymous() || name.is_empty() {
        panic!("Invalid parameters");
    }
    if self.used.contains_key(&root) || self.name_to_root.contains_key(&name) {
        panic!("Already registered");
    }
    self.used.insert(root.clone(), true);
    self.registered.insert(root.clone(), true);
    self.root_name.insert(root.clone(), name.clone());
    self.name_to_root.insert(name.clone(), root.clone());
}

割と良い線行ってます。Github Copilotは、Canister(Ethereumでいうスマートコントラクト)のステートを更新するときは、update属性を使うということも知っているようです。

Solidityのerror定義は、panic!で置き換えられていますね。本来は、Rustのenumで置き換えたいところですが、できるだけ早くコンパイルエラーを無くしたいので、panic!については目をつぶります。

#[update]

#[ic_cdk::update]

に修正します。

fn register_root(&mut self, root: Principal, name: String)

fn register_root(root: Principal) {

に修正します。

name_to_rootusedroot_nameは使っていないので、使っている部分を削除します。

nameも必要ないので、nameを使っている部分も削除します。

下記のコードになりました。

#[ic_cdk::update]
fn register_root(root: Principal) {
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if self.used.contains_key(&root) {
        panic!("Already registered");
    }
    self.registered.insert(root.clone(), true);
}

usedは削除したので、

self.used.contains_key(&root)

self.registered.contains_key(&root)

に置き換えます。

エラーマークのついているselfにカーソルを当てて、インラインチャットを呼び出し、下記のプロンプトを入力します。

/fix REGISTEREDを使うように修正して

正しく変換する提案をしてくれたので、Acceptで反映させます。

修正後のコードは下記のようになりました。

#[ic_cdk::update]
fn register_root(root: Principal) {
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Already registered");
    }
    REGISTERED.with_borrow_mut(|registered| registered.insert(root.clone(), true));
}

この関数を呼び出せるのは、Canisterの所有者だけというチェックが未実装ですが、あとで実装することにしましょう。

unregister_root

unregister_root()の修正前のコードは下記の通りです。

#[update]
fn unregister_root(&mut self, root: Principal) {
    if let Some(name) = self.root_name.get(&root) {
        self.name_to_root.remove(name);
    }
    self.registered.insert(root.clone(), false);
    self.root_name.insert(root.clone(), "".to_string());
}

先程と同様、update属性を変更し、引数のselfを削除します。

root_namename_to_rootを使っている部分を削除します。

Soliditymappingと違い、StableBTreeMapでエントリを削除するには、remove()を使うので、

self.registered.insert(root.clone(), false);

insertにカーソルを当てて、インラインチャットを起動し、下記のプロンプトを入力します。

/fix insertではなくremoveを使って

下記の変更が提案されたので、Acceptで受け入れます。

self.registered.remove(&root);

エラーマークのついているselfにカーソルを当てて、インラインチャットを呼び出し、下記のプロンプトを入力します。

/fix REGISTEREDを使って

下記の変更が提案されたので、Acceptで受け入れます。

REGISTERED.with_borrow_mut(|registered| registered.remove(&root));

修正後のコードは下記のようになりました。

#[ic_cdk::update]
fn unregister_root(root: Principal) {
    REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
}

この関数を呼び出せるのは、Canisterの所有者だけというチェックが未実装ですが、あとで実装することにしましょう。

また、intermediateが登録されているrootは削除できないというチェックも必要ですが、あとで実装することにしましょう。

register

register()は、rootintermediateを登録する関数です。登録の時に、intermediateの有効期限としてexpiryも登録します。

元のVerifyでは、register()のシグネチャは下記の通りです。

function register(
        bytes memory signature,
        address root,
        address intermediate,
        uint256 expiry,
        uint256 chainID,
        uint256 deadline
    ) external onlyRegistered(root)

元のVerifyでは、この関数を呼び出しているのが、確かにrootだということを検証するのに、signaturechainIDdeadlineを使っていますが、ICPでは、ic_cdk::caller()で、誰が関数を呼び出しているのか特定できるので、これらの引数は必要ありません。Solidityでもmsg.senderを使えばいい気がしますが。

register()でチェックしなければいけない項目は下記の通りです。

  • ic_cdk::caller()REGISTEREDに登録されていること
  • 引数のexpiryが未来であること
  • intermediateが登録済みの場合は、登録されているexpiryが未来であること

上記のチェックがOKなら、INTERMEDIATE_TO_ROOT, REGISTRY_EXPIRYに登録します。

Github Copilotは、register()を下記のように変換していました。

#[update]
fn register(
    &mut self,
    signature: Vec<u8>,
    root: Principal,
    intermediate: Principal,
    expiry: u64,
    chain_id: u64,
    deadline: u64,
) {
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if self.used.contains_key(&intermediate) {
        if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
            if root_identity != &root {
                panic!("Already registered");
            }
            if ic_cdk::api::time() > self.registry_expiry[root_identity][&intermediate] {
                panic!("Registry expired");
            }
        }
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&expiry.to_le_bytes());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._register(root, intermediate, expiry);
}

update属性を修正し、引数のselfsignaturerootchain_iddeadlineを削除します。関数の中身は全く役に立たないので、削除してしまいましょう。

関数の中身を削除したら、なんとGihtub Copilotが中身を提案してくれました。一部、間違っているけど、Acceptで受け入れます。

まずは、下記のようになりました。かなり良い線行ってます。

#[ic_cdk::update]
fn register(intermediate: Principal, expiry: u64) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    if INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.contains_key(&intermediate))
    {
        panic!("Already registered");
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.insert(intermediate.clone(), root.clone())
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}

intermediateINTERMEDIATE_TO_ROOTに登録済みならエラーにする部分が間違っているので、消して、下記のコメントを書きます。

// `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること

下記のコードが提案されました。間違っているけど、一旦受け入れます。

if let Some(expiry_map) = REGISTRY_EXPIRY.with_borrow(|registry_expiry| registry_expiry.get(&root)) {
        if let Some(expiry) = expiry_map.get(&intermediate) {
            if ic_cdk::api::time() > *expiry {
                panic!("Already registered");
            }
        }
    }

下記のように修正します。

if let Some(expiry) = REGISTRY_EXPIRY
        .with_borrow(|registry_expiry| registry_expiry.get(&(root.clone(), intermediate.clone())))
    {
        if ic_cdk::api::time() > expiry {
            panic!("Intermediate expired");
        }
    }

修正後のregister()は下記のようになりました。

#[ic_cdk::update]
fn register(intermediate: Principal, expiry: u64) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    // `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること
    if let Some(expiry) = REGISTRY_EXPIRY
        .with_borrow(|registry_expiry| registry_expiry.get(&(root.clone(), intermediate.clone())))
    {
        if ic_cdk::api::time() > expiry {
            panic!("Intermediate expired");
        }
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.insert(intermediate.clone(), root.clone())
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}

unregister

Github Copilotは、unregister()を下記のように変換していました。

#[update]
fn unregister(
    &mut self,
    signature: Vec<u8>,
    root: Principal,
    intermediate: Principal,
    chain_id: u64,
    deadline: u64,
) {
    if let Some(root_identity) = self.intermediate_to_root.get(&intermediate) {
        if root_identity != &root {
            panic!("Invalid parameters");
        }
    }
    if ic_cdk::api::time() > deadline {
        panic!("Signature expired");
    }
    if chain_id != ic_cdk::api::id() {
        panic!("Invalid parameters");
    }
    let mut data = Vec::new();
    data.extend_from_slice(&root.as_slice());
    data.extend_from_slice(&intermediate.as_slice());
    data.extend_from_slice(&self.nonces[&root].to_le_bytes());
    data.extend_from_slice(&chain_id.to_le_bytes());
    data.extend_from_slice(&deadline.to_le_bytes());
    let digest = ic_cdk::crypto::sha256(&data);
    let signer = ic_cdk::crypto::ecdsa_recover(&digest, &signature);
    if signer != root {
        panic!("Invalid signature");
    }
    *self.nonces.get_mut(&root).unwrap() += 1;
    self._unregister(root, intermediate);
}

register()と同様に、update属性を修正し、引数のselfsignaturerootchain_iddeadlineを削除します。関数の中身は全く役に立たないので、削除してしまいましょう。

期待通りに、Github Copilotが関数の中身を提案してくれました。先ほど間違えていたREGISTRY_EXPIRYのキーの処理もバッチリです。Acceptで受け入れます。今回は、修正する必要はありません。

修正後のunregister()は下記のようになりました。

#[ic_cdk::update]
fn unregister(intermediate: Principal) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.remove(&intermediate);
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.remove(&(root, intermediate)));
}

who_is

who_is()は、intermediaterootを返す関数です。ただし、有効期限を過ぎている場合は、Noneを返します。

Github Copilotは、who_is()を下記のように変換していました。

#[query]
fn who_is(&self, identity: Principal) -> Option<Principal> {
    if let Some(root) = self.intermediate_to_root.get(&identity) {
        if let Some(expiry_map) = self.registry_expiry.get(root) {
            if ic_cdk::api::time() > expiry_map[&identity] {
                return None;
            }
        }
        return Some(root.clone());
    }
    None
}

いつものように、query属性を修正し、引数のselfを削ります。identityという引数名は、intermediateにリファクタリングしておきましょう。

エラーマークのついているselfにカーソルを当てて、インラインチャットを呼び出し、次のプロンプトを実行します。

/fix INTERMEDIATE_TO_ROOTを使って

うまく修正できませんでした。selfを引数に追加しようとしています。IdentityRegistrystructとして認識しているので、impl IdentityRegistryを削って、インラインチャットで/fixとだけ入力して修正させてみました。今度は、下記のようにきちんと修正してくれました。

if let Some(root) = INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))

registry_expiryREGISTRY_EXPIRYを使うように修正してくれたのですが、キーをrootintermediatetupleを使うところが、まだ間違えて、rootのみを使っています。

エラーマークのついているrootにカーソルを当てて、インラインチャットに/fix tupleと入力しました。今度はちゃんと修正してくれました。expiry_mapの部分は直接修正します。

修正後のwho_is()は下記のようになりました。

#[ic_cdk::query]
fn who_is(intermediate: Principal) -> Option<Principal> {
    if let Some(root) = INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))
    {
        if let Some(expiry) = REGISTRY_EXPIRY
            .with_borrow(|registry_expiry| registry_expiry.get(&(root.clone(), intermediate)))
        {
            if ic_cdk::api::time() > expiry {
                return None;
            }
        }
        return Some(root.clone());
    }
    None
}

lib.rsの修正

lib.rsic_cdk::export_candid!();がエラーになっています。use candid::Principal;を支えていうメッセージが出ているので、それに従います。

明確に修正箇所がわかっている場合は、わざわざGithub Copilotを使う必要はありません。

clippyさんの警告の修正

全部のエラーが消えたら、clippyさんが7つほど警告を出しています。PrincipalCopyトレイトを実装しているので、clone()は必要ないとのことです。素直に従います。

これで、警告も全部消えました。

identity_registry.rs

identity_registry.rsの最終的なコードは下記のようになりました。

use std::cell::RefCell;

use candid::Principal;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};

thread_local! {
    static MEM_MGR: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));

    static REGISTERED: RefCell<StableBTreeMap<Principal, bool, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(0))))
    );

    static INTERMEDIATE_TO_ROOT: RefCell<StableBTreeMap<Principal, Principal, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(1))))
    );

    static REGISTRY_EXPIRY: RefCell<StableBTreeMap<(Principal, Principal), u64, VirtualMemory<DefaultMemoryImpl>>> = RefCell::new(
        StableBTreeMap::init(MEM_MGR.with_borrow(|mgr| mgr.get(MemoryId::new(2))))
    );
}

#[ic_cdk::update]
fn register_root(root: Principal) {
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Already registered");
    }
    REGISTERED.with_borrow_mut(|registered| registered.insert(root, true));
}

#[ic_cdk::update]
fn unregister_root(root: Principal) {
    REGISTERED.with_borrow_mut(|registered| registered.remove(&root));
}

#[ic_cdk::update]
fn register(intermediate: Principal, expiry: u64) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    // `intermediate`が登録済みの場合は、登録されている`expiry`が未来であること
    if let Some(expiry) =
        REGISTRY_EXPIRY.with_borrow(|registry_expiry| registry_expiry.get(&(root, intermediate)))
    {
        if ic_cdk::api::time() > expiry {
            panic!("Intermediate expired");
        }
    }
    INTERMEDIATE_TO_ROOT
        .with_borrow_mut(|intermediate_to_root| intermediate_to_root.insert(intermediate, root));
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.insert((root, intermediate), expiry));
}

#[ic_cdk::update]
fn unregister(intermediate: Principal) {
    let root = ic_cdk::caller();
    if root == Principal::anonymous() {
        panic!("Invalid parameters");
    }
    if !REGISTERED.with_borrow(|registered| registered.contains_key(&root)) {
        panic!("Root not registered");
    }
    INTERMEDIATE_TO_ROOT.with_borrow_mut(|intermediate_to_root| {
        intermediate_to_root.remove(&intermediate);
    });
    REGISTRY_EXPIRY
        .with_borrow_mut(|registry_expiry| registry_expiry.remove(&(root, intermediate)));
}

#[ic_cdk::query]
fn who_is(intermediate: Principal) -> Option<Principal> {
    if let Some(root) = INTERMEDIATE_TO_ROOT
        .with_borrow(|intermediate_to_root| intermediate_to_root.get(&intermediate))
    {
        if let Some(expiry) = REGISTRY_EXPIRY
            .with_borrow(|registry_expiry| registry_expiry.get(&(root, intermediate)))
        {
            if ic_cdk::api::time() > expiry {
                return None;
            }
        }
        return Some(root);
    }
    None
}

まだ、未実装部分は残っていますが、一旦ここまでとします。

Github Copilotにコミットメッセージを作ってもらいpushしておきます。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?