はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

今回は、トークンコントラクト自体に保護機能を組み込み、2つの補助ウォレットによって「Transfer・Approce」や「緊急時の退避・再設定」を制御できるNFKBTという仕組みを提案しているERC6809についてまとめていきます!

以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

Non-FungibleKeyBoundToken(NFKBT)は、KeyBoundToken(KBT=鍵に紐付くトークンという総称)の一種で、ERC721との互換性を保ちつつ「資産そのものに任意で付与できるセキュリティ」を備えるためのインターフェース標準です。 スマートコントラクト内で用いるAPIを定義し、とくにaddBindingsの基本動作を明確化します。 addBindingsは2つのKeyWallet(KeyWallet1/KeyWallet2)をNFKBTに紐付け、これらのKeyWalletがSafeTransfer(NFTを安全にTransferできるようにする手続き)の可否を制御します。 この過程で、NFKBTはユーザー本人またはオンチェーンの第三者(スマートコントラクトなど)が利用できるように、安全なapprove状態を設定します。

NFKBTのコアは、allowTransferとallowApprovalという「allow(許可)」機能を持つ2つの関数です。 これらはKeyWalletが呼び出し、HoldingWallet(資産を実際に保持するウォレット)が従来のERC721のtransferFromやapproveを呼べるように事前許可を与えます。 つまり、資産の移動や承認を直接行うのではなく、「この条件下でのみ送付や承認を許可する」という制御をKeyWallet側で設定できるようにするのが、このallow機能の役割です。

想定されるユースケースは、個人が自分のNFTに追加のセキュリティを持たせたい場合に加え、第三者のウォレット・ブローカー・銀行・保険・ギャラリー等への委託(consignment:一時預けや寄託)です。 NFKBTは、自己保管(self-custodial)という前提を維持したまま、資産そのものに保護機能を組み込むことで盗難や詐欺に対する耐性を高めます。

用語

用語 説明
KeyWallet 資産の使い方(TransferやApprovalの可否)を決める権限を持つウォレット。本文では2つ想定(KeyWallet1/KeyWallet2)。
HoldingWallet NFT資産そのものを保有するウォレット。KeyWalletからの許可があればtransferFromやapproveを実行できる。
SafeTransfer 安全な送付を指す一般語。本文ではKeyWalletが主導する安全な送付プロセスのことを指す。
DefaultBehaviors addBindingsを使わないときの従来のERC721としての既定動作。
DefaultValues 機能に与えられる既定値。ユーザーやdAppがカスタム値または既定値を渡せる。

主な関数とパラメータ

関数 目的 主なパラメータ
addBindings NFKBTにKeyWallet1/KeyWallet2を紐づけ、資産側のセキュリティ機能を有効化する。
allowTransfer 指定条件のもとでTransferを許可する「事前許可」を付与する。KeyWalletが呼ぶ。 _tokenId(対象トークンID)、_time(許可の時間条件)、_address(許可相手アドレス)、_anyToken(任意トークン許可の有無)
allowApproval Approval(承認)を許可する「事前許可」を付与する。KeyWalletが呼ぶ。 _time(許可の時間条件)、_numberOfTransfers(許可される送付回数)
safeFallback HoldingWalletに不正アクセスの疑いがある場合、KeyWalletから操作を行うことで、資産をもう一方のKeyWalletに移動させて安全を確保できます。
resetBindings どちらかのKeyWalletに不正アクセスの疑いがあるとき、KeyWalletからセキュリティ設定をリセットして再設定可能にする。 呼び出し元:KeyWallet1またはKeyWallet2
transferFrom/approve 従来のERC721の機能。KeyWalletによるallow...で事前許可がある場合、HoldingWalletがこれらを実行できる。 (ERC721に準拠)

動機

ブロックチェーンの利用者は年齢や背景が多様で学習速度もまちまちです。 自己保管(self-custodial)資産は「自分で完全に管理できる」という大きな利点がある一方、誤操作・鍵の流出・詐欺被害が起きたときに取り戻せないという重大な欠点も抱えます。 米国連邦取引委員会(FTC)には2021年1月〜2022年3月のあいだに46,000件超の暗号資産詐欺報告が寄せられ、消費者損失は10億ドル超に達しました。 こうした被害は規制強化を招きがちですが、強い規制はこの領域の中核的価値観と相容れない面もあります。 中央集権・分散型の双方でセキュリティ向上の試みはありましたが、両者の長所を取り込み短所を避ける決定打はありませんでした。

そこで観点を「ウォレットをどう守るか?」から「資産そのものをどう守るか?」に切り替えます。 ウォレットを作るのは無料ですが、価値があるのは資産側です。 この発想からKBTが生まれ、ユーザーが望む範囲で完全に任意で資産へセキュリティを持たせられる設計を目指しました。 機能を正しく有効化しておけば、たとえシードフレーズや秘密鍵が流出しても資産に設定した保護機能がそのまま働き続けます。

NFKBT(Non-FungibleKeyBoundToken)は、KBT(KeyBoundToken:鍵に紐づくトークン)の一種で、ERC721と互換性を保ったまま「資産そのものに任意で追加できる保護機能」を提供するインターフェースです。 ERC6809は、スマートコントラクトで用いるAPIを規格として提示します。 addBindingsは、2つのKeyWallet(KeyWallet1/KeyWallet2)をNFKBTに登録し、これらのKeyWalletがSafeTransfer(NFTを安全に送付できるようにする手続き)を許可するかどうかを管理します。 あわせて、ユーザー本人やオンチェーンの第三者(スマートコントラクトなど)が利用できるように、NFKBTのapprove(承認)を安全に設定します。

NFKBTでは、allowTransferとallowApprovalという2つの「許可(allow)」関数を用います。 これらはKeyWalletから呼び出され、HoldingWallet(資産を保持するウォレット)が、ERC721のtransferFromやapproveを実行できるようにする事前許可を与えます。 資産の保管はHoldingWalletが担い、送付や承認の条件設定はKeyWalletが担うという役割分担になります。 なお、addBindingsを使わない場合は、通常のERC721と同じ挙動で動作します。

想定されるユースケースは、個人が自分のNFTに追加の保護を付けたいケースに限らず、第三者ウォレット・ブローカー・銀行・保険・ギャラリー等への委託(consignment)も含みます。

利点 説明
完全に分散型 保護機能を有効化しても第三者が資産へアクセスしません。自己保管の考えに合致します。
スケーラビリティ 地域制限やアカウント作成が不要です。専用ハードウェアの配布や在庫などの制約も避けられます。
任意に利用可能 保護機能はオン・オフや細かな調整が可能で、ユーザーの希望に合わせて使えます。
既定動作の維持 addBindingsを使わなければ従来のERC721と同じです。有効化しても既定値やカスタム値で挙動を調整できます。
保護の強化 allowTransfer(_tokenId、_time、_address、_anyToken)とallowApproval(_time、_numberOfTransfers)で、KeyWallet経由の事前許可が必須になります。さらにsafeFallbackとresetBindingsを備え、適切に使えば、従来のERC721で発生しやすいリスクやトラブルを防止できます。
安全な退避と再設定 HoldingWalletに不正アクセスの疑いがあるときは、KeyWalletの操作で資産をもう一方のKeyWalletに移し安全を確保できます(safeFallback)。KeyWallet側に疑いがあるときは、設定を一度リセットして新しいKeyWalletを紐づけ直せます(resetBindings)。単一の故障点に依存しない設計です。
匿名性の維持 個人情報の提出や配送情報を前提とせず、オンチェーンで仮名のまま利用できます。
低コスト かかる費用はオンチェーン手数料(GWEI)に連動します。単独の保護手段として現実的なコストです。
環境負荷が小さい 中央サーバや物理デバイスの製造・配送が不要で、EthereumのPoS移行とも整合します。

仕様

ERC6809は、Non-Fungible Key Bound Token(NFKBT)の実装を前提としたIKBT721インターフェースを定義しています。 ERC721と互換性を持ちつつ、「KeyWallet」と「HoldingWallet」による二重保護構造を導入することで、資産(トークン)自体に安全機能を付与します。

インターフェース

javascript
interface IKBT721 {
    event AccountSecured(address indexed _account, uint256 _noOfTokens);
    event AccountResetBinding(address indexed _account);
    event SafeFallbackActivated(address indexed _account);
    event AccountEnabledTransfer(
        address _account,
        uint256 _tokenId,
        uint256 _time,
        address _to,
        bool _anyToken
    );
    event AccountEnabledApproval(
        address _account,
        uint256 _time,
        uint256 _numberOfTransfers
    );
    event Ingress(address _account, uint256 _tokenId);
    event Egress(address _account, uint256 _tokenId);

    struct AccountHolderBindings {
        address firstWallet;
        address secondWallet;
    }

    struct FirstAccountBindings {
        address accountHolderWallet;
        address secondWallet;
    }

    struct SecondAccountBindings {
        address accountHolderWallet;
        address firstWallet;
    }

    struct TransferConditions {
        uint256 tokenId;
        uint256 time;
        address to;
        bool anyToken;
    }

    struct ApprovalConditions {
        uint256 time;
        uint256 numberOfTransfers;
    }

    function addBindings(
        address _keyWallet1,
        address _keyWallet2
    ) external returns (bool);

    function getBindings(
        address _account
    ) external view returns (AccountHolderBindings memory);

    function resetBindings() external returns (bool);

    function safeFallback() external returns (bool);

    function allowTransfer(
        uint256 _tokenId,
        uint256 _time,
        address _to,
        bool _allTokens
    ) external returns (bool);

    function getTransferableFunds(
        address _account
    ) external view returns (TransferConditions memory);

    function allowApproval(
        uint256 _time,
        uint256 _numberOfTransfers
    ) external returns (bool);

    function getApprovalConditions(
        address account
    ) external view returns (ApprovalConditions memory);

    function getNumberOfTransfersAllowed(
        address _account,
        address _spender
    ) external view returns (uint256);

    function isSecureWallet(address _account) external returns (bool);

    function isSecureToken(uint256 _tokenId) external returns (bool);
}

イベント

AccountSecured
javascript
event AccountSecured(address _account, uint256 _amount);

addBindings関数を呼び出して、ウォレットをセキュリティ保護したときに発行されるイベント。

ユーザーがaddBindingsを呼ぶことで、2つのKeyWalletが登録されます。 このイベントは、保護対象のアカウント(_account)がセキュア化されたことを示します。 第二引数は、セキュリティ保護時点のトークン残高を表します。

パラメータ

  • _account
    • セキュア化されたアカウントアドレス。
  • _amount
    • セキュア化時点のトークン保有量。
AccountResetBinding
javascript
event AccountResetBinding(address _account);

resetBindings関数の呼び出し時に発行されるイベント。

KeyWalletの1つが危険にさらされた場合、この関数を使って登録をリセットします。 このイベントは、アカウントに紐づくKeyWallet設定がリセットされたことを通知します。

パラメータ

  • _account
    • リセット対象のアカウントアドレス。
SafeFallbackActivated
javascript
event SafeFallbackActivated(address _account);

safeFallback関数の呼び出し時に発行されるイベント。

HoldingWalletが危険にさらされた場合、KeyWalletの一方からすべての資産をもう一方に移動させます。 このイベントは、資産の移動が開始されたことを示します。

パラメータ

  • _account
    • 処理対象のアカウントアドレス。
AccountEnabledTransfer
javascript
event AccountEnabledTransfer(
    address _account,
    uint256 _tokenId,
    uint256 _time,
    address _to,
    bool _anyToken
);

allowTransfer関数の呼び出し時に発行されるイベント。

KeyWalletが特定条件下でTransferを許可した時に発行されます。 特定のトークンID、期間、宛先、または任意トークンの送付を許可したことを示します。

パラメータ

  • _account
    • 許可を設定したアカウント。
  • _tokenId
    • 対象トークンID(0の場合は制限なし)。
  • _time
    • 許可の有効期間(ブロック時間)。
  • _to
    • Transferを許可するアドレス。
  • _anyToken
    • 任意のトークンを送付可能かどうかのフラグ。
AccountEnabledApproval
javascript
event AccountEnabledApproval(
    address _account,
    uint256 _time,
    uint256 _numberOfTransfers
);

allowApproval関数の呼び出し時に発行されるイベント。

KeyWalletが、特定期間およびTransfer回数に基づく承認を許可した時に発行されます。

パラメータ

  • _account
    • 承認許可を設定したアカウント。
  • _time
    • 承認の有効期限(ブロック時間)。
  • _numberOfTransfers
    • 許可されるTransfer回数(0は無制限)。
Ingress
javascript
event Ingress(address _account, uint256 _tokenId);

アカウントが新たにトークンを保有したときに発行されるイベント。 指定アカウントがトークンを初めて受け取ったことを通知します。

パラメータ

  • _account
    • 新規保有者のアカウント。
  • _tokenId
    • 受け取ったトークンID。
Egress
javascript
event Egress(address _account, uint256 _tokenId);

アカウントがすべてのトークンを手放した時に発行されるイベント。

指定アカウントがすべての保有トークンをTransferして保有者でなくなったことを通知します。

パラメータ

  • _account
    • 保有を終了したアカウント。
  • _tokenId
    • 最後にTransferしたトークンID。

構造体

AccountHolderBindings
javascript
struct AccountHolderBindings {
    address firstWallet;
    address secondWallet;
}

アカウントに紐づく2つのKeyWalletを管理する構造体。

HoldingWalletに対して、2つのKeyWalletを安全設定として保持します。

パラメータ

  • firstWallet
    • 登録された1つ目のKeyWallet。
  • secondWallet
    • 登録された2つ目のKeyWallet。
TransferConditions
javascript
struct TransferConditions {
    uint256 tokenId;
    uint256 time;
    address to;
    bool anyToken;
}

Transfer許可条件を定義する構造体。

allowTransfer関数で設定される、送付対象・有効期間・宛先・任意トークン可否を保持します。

パラメータ

  • tokenId
    • 対象となるトークンID。
  • time
    • Transfer可能な期限(ブロック時間)。
  • to
    • Transfer許可されたアドレス。
  • anyToken
    • 任意のトークン送付を許可するかどうか。
ApprovalConditions
javascript
struct ApprovalConditions {
    uint256 time;
    uint256 numberOfTransfers;
}

Approval許可条件を定義する構造体。

allowApproval関数で設定される、承認有効期間とTransfer回数制限を保持します。

パラメータ

  • time
    • 承認が有効な期限。
  • numberOfTransfers
    • 承認されたTransferの回数。

関数

addBindings
javascript
function addBindings(address _keyWallet1, address _keyWallet2) external returns (bool);

2つのKeyWalletを登録し、アカウントをセキュア化する関数。

呼び出し元(Holderアカウント)をKeyWallet1/KeyWallet2と結びつけ、アカウントを保護対象にします。 登録完了時にAccountSecuredイベントが発行されます。

以下の条件に該当する場合、処理はrevertします。

  • 呼び出し元がHolderでない場合。
  • すでにセキュリティ保護済みの場合。
  • 2つのKeyWalletが同一アドレス。
  • 呼び出し元とKeyWalletのいずれかが同じ。
  • KeyWalletが0x0(無効アドレス)。
  • 既に他のHolderにKeyWalletとして登録済み。

引数

  • _keyWallet1
    • 1つ目のKeyWallet。
  • _keyWallet2
    • 2つ目のKeyWallet。

戻り値

  • bool
    • 成功時はtrue、失敗時はfalse。
getBindings
javascript
function getBindings(address _account) external view returns (AccountHolderBindings memory);

指定されたアカウントに紐づくKeyWallet情報を取得する関数。

_accountアドレスに対して登録済みのKeyWallet1とKeyWallet2を、AccountHolderBindings構造体の形式で返します。 これにより、対象のアカウントがどのKeyWalletと連携しているかを確認できます。

引数

  • _account
    • KeyWallet情報を取得したいアカウントアドレス。

戻り値

  • AccountHolderBindings
    • firstWalletおよびsecondWalletを含む構造体。
resetBindings
javascript
function resetBindings() external returns (bool);

既存のKeyWallet設定をリセットする関数。

KeyWalletの1つが不正アクセスされた、または使用不能になった場合に利用します。 KeyWallet(_keyWallet1または_keyWallet2)から呼び出され、対象HolderのKeyWallet設定をリセットします。 呼び出し後、Holderは再度addBindingsを使用して新しいKeyWalletを設定できます。 処理成功時にはAccountResetBindingイベントが発行されます。 KeyWallet以外のアドレスから呼び出された場合はrevertします。

戻り値

  • bool
    • 成功時はtrue、失敗時はfalse。
safeFallback
javascript
function safeFallback() external returns (bool);

HoldingWalletに不正アクセスが発生した時に資産を安全なKeyWalletへ移す関数。

Holderアカウントが危険にさらされたとき、KeyWalletのいずれかから呼び出して資産をもう一方のKeyWalletへ移動します。 これにより、HoldingWalletが完全に乗っ取られても、KeyWallet側で資産を安全に退避できます。 実行後、SafeFallbackActivatedイベントが発行されます。 KeyWallet以外のアドレスが呼び出した場合はrevertします。

戻り値

  • bool
    • 成功時はtrue、失敗時はfalse。
allowTransfer
javascript
function allowTransfer(
    uint256 _tokenId,
    uint256 _time,
    address _to,
    bool _anyToken
) external returns (bool);

KeyWalletから特定のTransfer条件を事前許可する関数。

transferFromやsafeTransferFromの呼び出し前に使用され、特定のトークン送付条件を事前に許可します。 この許可により、HoldingWalletが指定の条件を満たすTransferを安全に実行可能になります。 パラメータを柔軟に設定でき、0値を指定すると制限を解除できます。

  • _tokenId = 0 トークンIDの制限なし
  • _time = 0 有効期限の制限なし
  • _to = 0x0 宛先制限なし
  • _anyToken = true すべてのトークン送付を許可

条件が設定されると、AccountEnabledTransferイベントが発行されます。 KeyWallet以外が呼び出した場合、またはトークンの所有者がHolderでない場合はrevertします。

引数

  • _tokenId
    • Transferを許可するトークンID。
  • _time
    • Transfer許可の有効期限(ブロック時間)。
  • _to
    • Transferを許可するアドレス。
  • _anyToken
    • 任意のトークンTransferを許可するかどうか。

戻り値

  • bool
    • 成功時はtrue、失敗時はfalse。
getTransferableFunds
javascript
function getTransferableFunds(address _account) external view returns (TransferConditions memory);

指定アカウントのTransfer許可条件を取得する関数。

allowTransferで設定された条件(トークンID、有効期限、宛先、任意許可)を、構造体TransferConditionsで返します。 これにより、どの条件下でTransferが実行可能かを確認できます。

引数

  • _account
    • Transfer条件を取得したいアカウント。

戻り値

  • TransferConditions
    • 設定されたTransfer条件の構造体。
allowApproval
javascript
function allowApproval(
    uint256 _time,
    uint256 _numberOfTransfers
) external returns (bool);

KeyWalletからApproval(承認)条件を事前許可する関数。

approveまたはsetApprovalForAllの実行前に使用し、指定期間内に指定回数分のTransferを許可します。 _numberOfTransfersに0を指定した場合は、期間中無制限に許可されます。 設定が完了するとAccountEnabledApprovalイベントが発行されます。 KeyWallet以外のアドレスが呼び出した場合はrevertします。

引数

  • _time
    • 承認が有効な期限(ブロック時間)。
  • _numberOfTransfers
    • 承認されたTransfer回数(0は無制限)。

戻り値

  • bool
    • 成功時はtrue、失敗時はfalse。
getApprovalConditions
javascript
function getApprovalConditions(address _account) external view returns (ApprovalConditions memory);

指定アカウントの承認条件を取得する関数。

allowApprovalで設定された承認条件を、ApprovalConditions構造体で返します。 構造体には、有効期限(time)と許可されたTransfer回数(numberOfTransfers)が含まれます。

引数

  • _account
    • 条件を取得したいアカウントアドレス。

戻り値

  • ApprovalConditions
    • 承認条件を含む構造体。
getNumberOfTransfersAllowed
javascript
function getNumberOfTransfersAllowed(
    address _account,
    address _spender
) external view returns (uint256);

指定アカウントが特定のSpenderに許可しているTransfer回数を取得する関数。

allowApprovalで設定された回数制限を確認するために使用します。 この関数により、Spenderがあと何回Transfer可能かを把握できます。

引数

  • _account
    • 承認を設定したHolderアカウント。
  • _spender
    • 承認を受けているアドレス。

戻り値

  • uint256
    • 残りTransfer回数。
isSecureWallet
javascript
function isSecureWallet(address _account) external returns (bool);

指定アカウントがセキュリティ機能を有効化しているかを判定する関数。

addBindingsを使用してKeyWalletを設定している場合、この関数はtrueを返します。 未設定の場合や通常のウォレットはfalseとなります。

引数

  • _account
    • 判定対象のアカウント。

戻り値

  • bool
    • セキュリティ有効化状態(true=有効、false=無効)。
isSecureToken
javascript
function isSecureToken(uint256 _tokenId) external returns (bool);

特定のトークンがセキュリティ保護対象かどうかを判定する関数。

addBindingsによってセキュア化されたアカウントに属するトークンはtrueを返します。 通常のERC721トークンはfalseとなります。

引数

  • _tokenId
    • 判定対象のトークンID。

戻り値

  • bool
    • セキュリティ保護状態(true=保護済み、false=未保護)。

補足

NFKBTの各技術判断は、ERC721との一貫性と互換性を保ちつつ、自己保管(self-custodial)で使えるセキュリティ機能をユーザーに提供することを重視しています。 NFKBTはERC721の機能をそのまま受け継ぐことで、既存のNFT対応dAppに求められる要件を満たし、既存エコシステム側の調整を必要としない形で利用できるようにしています。 その結果、ユーザーはNFKBTを「従来の動作」で使うか、NFKBTのセキュリティ機能を有効にして使うかを選べます。 これにより、実装が即時に進められ、既存の分散型エコシステムが変更を行う必要がなくなります。

開発者とユーザーの双方にとって扱いやすいよう、allowTransferとallowApprovalは成功時にboolを返し、失敗時はrevertします。 これはERC721で一般的な挙動に合わせた決定です。

互換性

KBT(KeyBoundToken)は、既存のトークン標準やウォレットと互換を持つように設計されています。 既存のトークンやウォレットは、NFKBTが導入されても従来どおり機能して影響を受けません。 NFKBTはERC721に準拠するため、セキュリティ機能を使わない限り従来同様の使い方ができ、機能を有効にする場合でも既存のワークフローを阻害しない構成になっています。

テスト

テスト一式はassetsディレクトリに配置されています。 平均的なガスについては以下のとおりです。

機能 平均ガス使用(原文記載)
addBindings 155,096
resetBindings 30,588
safeFallback 72,221(Holderが保有するNFT数に依存)
allowTransfer 50,025
allowApproval 44,983

これらの値は、各機能を実行した際の平均的なコスト感を示す目安です。 特にsafeFallbackは、保有NFTの数によって移動対象が増減するため、使用量が変動します。

参考実装

実装はassetsディレクトリに配置されています。

以下はフロー図です。

参考実装の図

このフロー図は、HoldingWallet・KeyWallet1・KeyWallet2・IKBT721コントラクトの間で行われる代表的な操作(addBindings・allowTransfer・allowApproval・safeFallback・resetBindings)のやり取りを時系列で表しています。

ステップ1:ウォレットの保護設定(addBindings)

  1. HoldingWallet(NFTを保有するアカウント)が、セキュリティ機能を有効化するために、addBindingsを呼び出します。
  2. 引数として2つのKeyWalletアドレス(_keyWallet1, _keyWallet2)を指定します。
  3. コントラクトはこの設定を登録し、HoldingWalletが「セキュアアカウント」として扱われるようになります。
  4. 処理成功後、AccountSecuredイベントが発行されます。

結果、HoldingWalletはNFKBTとしてセキュリティが有効化され、KeyWallet1とKeyWallet2が紐づけられます。

ステップ2:Transfer許可設定(allowTransfer)

  1. KeyWallet(1または2のいずれか)が、allowTransferを呼び出します。
  2. パラメータには以下の4つを指定します。
    • _tokenId 対象となるNFTのID(0を指定すればすべて許可)。
    • _time Transferが許可される期限。
    • _to Transferを許可する宛先。
    • _anyToken すべてのトークン送付を許可するかどうか。
  3. コントラクトは許可条件を登録し、AccountEnabledTransferイベントを発行します。

結果、HoldingWalletは、指定条件のもとでtransferFromを実行できるようになります。

ステップ3:Approval許可設定(allowApproval)

  1. KeyWalletがallowApprovalを呼び出します。
  2. パラメータには以下を指定します。
    • _time 承認の有効期限。
    • _numberOfTransfers 許可するTransfer回数(0は無制限)
  3. コントラクトは条件を保存し、AccountEnabledApprovalイベントを発行します。

結果、HoldingWalletは、指定期間・指定回数の範囲でapproveまたはsetApprovalForAllを行うことができます。

ステップ4:トークンの送付・承認(transferFrom / approve)

  1. HoldingWalletが、transferFromまたはapproveを呼び出します。
  2. コントラクトは事前に設定された許可条件をチェックします。
  3. 条件が満たされていれば実行、満たされなければrevertします。
  4. 実行時、標準のTransferまたはApprovalイベントが発行されます。
    • transferFromでは、許可回数を1減らし、0になれば承認を取り消します。
    • approveやsetApprovalForAllも同様に、期間や回数制限を適用します。

ステップ5:不正アクセス検知時の資産移動(safeFallback)

  1. KeyWalletが、HoldingWalletの不正アクセスを検知した場合、safeFallbackを呼び出します。
  2. コントラクトはHoldingWalletの保有資産を、もう一方のKeyWalletへ移動します。
  3. 処理完了後、SafeFallbackActivatedイベントが発行されます。

結果、HoldingWalletが乗っ取られても、資産はKeyWallet側に避難できます。

ステップ6:KeyWalletの入れ替え(resetBindings)

  1. KeyWallet1またはKeyWallet2が、もう一方のKeyWalletに問題があると判断した場合、resetBindingsを呼び出します。
  2. コントラクトはKeyWallet設定を初期化し、HoldingWalletが再びaddBindingsを実行できる状態に戻します。
  3. 処理完了後、AccountResetBindingイベントが発行されます。

結果、新しいKeyWalletを登録できるようになり、単一障害点を防止します。

全体フローまとめ

ステップ 呼び出し元 関数 目的 結果
1 HoldingWallet addBindings KeyWalletを登録 セキュリティ機能を有効化
2 KeyWallet allowTransfer Transfer条件を許可 条件付き送付を承認
3 KeyWallet allowApproval Approval条件を許可 条件付き承認を許可
4 HoldingWallet transferFrom / approve 実際の送付・承認処理 条件に応じた操作を実行
5 KeyWallet safeFallback HoldingWalletの危険時に退避 資産をKeyWalletへ移動
6 KeyWallet resetBindings KeyWalletのリセット 新しいKeyWalletを登録可能に

この一連の流れにより、NFKBTは「資産を直接保護する」仕組みをスマートコントラクト上で完結させています。 ユーザーはウォレット単位ではなくトークン単位で安全性を制御でき、鍵漏えいやウォレット乗っ取りといった問題に対して、柔軟かつオンチェーンでの対応が可能になります。

セキュリティ

KeyWalletの設計(3ウォレット構成の理由)

NFKBTでは、addBindings関数を呼び出す時に2つのKeyWalletを登録する必要があります。 これにより、NFKBTは以下の3ウォレット構成で動作します。

種類 説明
HoldingWallet トークン(NFKBT)を実際に保有するウォレット。
KeyWallet1 保護・許可・復旧操作を行うウォレット(1つ目)。
KeyWallet2 同上(2つ目)。

このように「3ウォレット構成」を採用した理由は以下です。

  • 登録を同時に行うことでガスコストを削減できること。
  • 1つずつKeyWalletを追加する場合に起きやすい設定ミスを防げること。
  • 安全機能(safeFallbackなど)の悪用を防止するため。

もしKeyWalletを複数回に分けて追加できる仕様にしてしまうと、設定ミスやガスコストの増大が発生するだけでなく、safeFallback機能を悪用される恐れも生じます。 そのため、NFKBTでは必ず2つのKeyWalletを同時に登録する仕様としています。

想定シナリオごとの対応策

NFKBTは、どのウォレットが侵害されたかによって、ユーザーが取るべき行動を明確に定義しています。

シナリオ 想定される状況 推奨アクション 使用する関数
HoldingWalletが侵害された 保有ウォレットが乗っ取られた・アクセス不能 KeyWalletから資産を退避 safeFallback
KeyWallet1または2が侵害された 保護ウォレットの1つが乗っ取られた・アクセス不能 KeyWalletからセキュリティ設定をリセット resetBindings

HoldingWalletが侵害された場合(safeFallback)

HoldingWalletが不正アクセスを受けた、またはアクセス不能になった場合、ユーザーはKeyWalletのどちらかからsafeFallback関数を呼び出すことで、資産をもう一方のKeyWalletへ退避できます。

safeFallbackの呼び出しにより、NFKBTはHoldingWalletからKeyWalletへ安全に移動します。 この仕組みにより、HoldingWalletが乗っ取られても、資産が攻撃者の手に渡る前に保護できます。 特に、NFKBTが呼び出し元のKeyWallet自身に送られないように設計されている点が重要です。 もし呼び出し元KeyWalletに移動してしまうと、攻撃者がKeyWalletを奪っていた場合、すべてのNFKBTが一括で奪われる危険があります。

そのため、safeFallbackでは必ずもう一方のKeyWalletに資産を移すよう設計されています。

KeyWalletが侵害された場合(resetBindings)

KeyWallet1またはKeyWallet2が不正アクセスを受けた場合、残っているKeyWalletからresetBindings関数を呼び出します。

resetBindingsの呼び出しにより、コントラクトに登録されているKeyWallet情報がリセットされ、NFKBTは一時的に通常のERC721トークンのように動作します。 これにより、新しいKeyWalletの再設定(addBindings)が可能になります。

この関数は、KeyWalletのみが呼び出せるようになっています。 もしHoldingWalletがresetBindingsを呼び出せる設計であれば、攻撃者がHoldingWalletを乗っ取った瞬間にセキュリティ機能を解除し、NFKBTを自由にTransferできてしまいます。 このような事態を防ぐため、resetBindingsの呼び出し権限はKeyWalletのみに限定されています。

3つのウォレットのうち2つが同時に侵害された場合、NFKBTの所有者にできることはありません。 しかし、1つだけ侵害された場合は、NFKBTによってリカバリの機会(second chance)が与えられます。 これは、従来のNFT標準では存在しなかった大きな利点です。

allowTransferのセキュリティ設計

allowTransferは、NFKBTの送付を安全に実行するための関数であり、ユーザーがTransfer条件を細かく指定できます。 また、dAppが既定値(Default Values)を設定すれば、従来のERC721と同じ動作(Default Behaviors)を再現することも可能です。 この柔軟性により、ユーザーは「高い制限を課す安全なTransfer」から「無制限Transfer」まで、自分に合ったセキュリティレベルを選択できます。

パラメータと挙動

この関数は4つのパラメータを取り、組み合わせによって異なるレベルの保護を実現します。

パラメータ 型 説明
_tokenId uint256 対象となるNFKBTのID。特定のトークンに制限。
_time uint256 Transferが可能な期間(ブロック数単位)。現在のタイムスタンプを基準に指定。
_address address 送付を許可するアドレス。
_anyToken bool trueの場合、制限を無効化し従来のERC721のように振る舞う。

_anyTokenがfalseの場合は、上記3つの条件(トークンID、期限、宛先)をすべて満たしたTransferが行われます。 一方、_anyTokenがtrueの場合は、従来のERC721と同様のTransferが可能です。

呼び出し要件

allowTransferはKeyWallet(1または2)から呼び出す必要があります。 これにより、HoldingWalletが勝手にTransferを行えないように制御され、知らない間いに資産が動かされるリスクを軽減します。

allowApprovalのセキュリティ設計

allowApprovalは、第三者(dAppやスマートコントラクト)にNFKBTの利用を許可する時の安全性を高める関数です。 悪意のあるdAppによるトークンの不正操作(例:ドレイン攻撃)を防ぐために設計されています。

パラメータと挙動

この関数は2つのパラメータを取り、組み合わせによって許可の範囲を柔軟に制御します。

パラメータ 型 説明
_time uint256 承認が有効な期間(ブロック数単位)。指定時間を過ぎると無効化。
_numberOfTransfers uint256 承認された第三者が実行できるTransfer回数。0の場合は無制限。

これにより、ユーザーは「どのくらいの期間・どのくらいの回数、第三者が操作できるか」を明示的に制限できます。

呼び出し要件

allowApprovalもKeyWallet(1または2)から呼び出す必要があります。 HoldingWalletが直接approveを呼び出せるわけではなく、KeyWalletによる事前許可を経ることで安全性を確保します。

最後に

今回は「トークンコントラクト自体に保護機能を組み込み、2つの補助ウォレットによって「Transfer・Approce」や「緊急時の退避・再設定」を制御できるNFKBTという仕組みを提案しているERC6809」についてまとめてきました! いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

他の媒体でも情報発信しているのでぜひ他も見ていってください!