はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、NFTに関するライセンスをオンチェーンで管理する仕組みを提案しているERC5218についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIPについてまとめています。
概要
ERC5218は、NFTに関するライセンスを管理するためのAPI仕様を提案しています。
NFTに付随する著作権ライセンスを、オンチェーンで発行・譲渡・取消できるようにする仕組みを提供します。
ただし、ライセンスの法律的な内容まではこの規格では定義しておらず、あくまでその情報を記録・参照するためのインターフェースを提供しています。
動機
ERC721はNFTの所有権を管理する規格ですが、例えば画像や音楽といったオフチェーンの著作物をNFTに関連づける場合、法律的に意味のあるリンクが必要になります
今のところ、多くのNFTプロジェクトではオフチェーンの文書でライセンスを表現しており、NFT自体にはその情報が含まれていないのが現状です。
そのため、例えば「このNFTを持っている人はその画像を商用利用できるのか?」といったことが不明確になってしまいます。
さらに、既存の規格(ERC721の所有権やERC4907のレンタル)では権利管理には対応できていません。
ERC5218では、この課題に対してオンチェーン上で著作権などの権利を管理できるようにしています。
ライセンスの仕組み
ERC5218では、以下のようなライセンス管理を可能にします。
- NFT保有者が基本ライセンスを持つ。
- NFT保有者はそのライセンスに基づいて他人にサブライセンスを発行できる。
- ライセンスはNFTと一緒に譲渡される(=NFTを誰かに送ると、ライセンスも一緒に移動)。
- クリエイターが決めた条件にしたがって、ライセンスを取り消すことも可能。
この仕組みを通じて、ライセンスの発行やその状態(有効・無効など)をすべてオンチェーンで管理できるようになります。
URIによるライセンスの参照
各ライセンスには、**URI(外部リンク)**が紐づけられます。
このURIには以下のような情報が含まれます。
- 人間が読める法的なライセンス文(例:著作権の利用条件を説明する文書)。
- 機械が読める要約情報(例:CC RELという形式で記述されたデータ)。
これにより、ユーザーは自分が何の権利を持っているのかを明確に把握でき、NFTの作成者や第三者も不正利用が行われていないか確認しやすくなります。
仕様
## インターフェース
ERC5218に準拠したコントラクトは、IERC5218インターフェースを実装する必要があります。
これは、ERC721をベースにしつつ、ライセンスの発行・譲渡・取消などを管理するための関数やイベントが追加されています。
pragma solidity ^0.8.0;
/// @title ERC-5218: NFT Rights Management
interface IERC5218 is IERC721 {
/// @dev This emits when a new license is created by any mechanism.
event CreateLicense(uint256 _licenseId, uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string _uri, address _revoker);
/// @dev This emits when a license is revoked. Note that under some
/// license terms, the sublicenses may be `implicitly` revoked following the
/// revocation of some ancestral license. In that case, your smart contract
/// may only emit this event once for the ancestral license, and the revocation
/// of all its sublicenses can be implied without consuming additional gas.
event RevokeLicense(uint256 _licenseId);
/// @dev This emits when the a license is transferred to a new holder. The
/// root license of an NFT should be transferred with the NFT in an ERC721
/// `transfer` function call.
event TransferLicense(uint256 _licenseId, address _licenseHolder);
/// @notice Check if a license is active.
/// @dev A non-existing or revoked license is inactive and this function must
/// return `false` upon it. Under some license terms, a license may become
/// inactive because some ancestral license has been revoked. In that case,
/// this function should return `false`.
/// @param _licenseId The identifier for the queried license
/// @return Whether the queried license is active
function isLicenseActive(uint256 _licenseId) external view returns (bool);
/// @notice Retrieve the token identifier a license was issued upon.
/// @dev Throws unless the license is active.
/// @param _licenseId The identifier for the queried license
/// @return The token identifier the queried license was issued upon
function getLicenseTokenId(uint256 _licenseId) external view returns (uint256);
/// @notice Retrieve the parent license identifier of a license.
/// @dev Throws unless the license is active. If a license doesn't have a
/// parent license, return a special identifier not referring to any license
/// (such as 0).
/// @param _licenseId The identifier for the queried license
/// @return The parent license identifier of the queried license
function getParentLicenseId(uint256 _licenseId) external view returns (uint256);
/// @notice Retrieve the holder of a license.
/// @dev Throws unless the license is active.
/// @param _licenseId The identifier for the queried license
/// @return The holder address of the queried license
function getLicenseHolder(uint256 _licenseId) external view returns (address);
/// @notice Retrieve the URI of a license.
/// @dev Throws unless the license is active.
/// @param _licenseId The identifier for the queried license
/// @return The URI of the queried license
function getLicenseURI(uint256 _licenseId) external view returns (string memory);
/// @notice Retrieve the revoker address of a license.
/// @dev Throws unless the license is active.
/// @param _licenseId The identifier for the queried license
/// @return The revoker address of the queried license
function getLicenseRevoker(uint256 _licenseId) external view returns (address);
/// @notice Retrieve the root license identifier of an NFT.
/// @dev Throws unless the queried NFT exists. If the NFT doesn't have a root
/// license tethered to it, return a special identifier not referring to any
/// license (such as 0).
/// @param _tokenId The identifier for the queried NFT
/// @return The root license identifier of the queried NFT
function getLicenseIdByTokenId(uint256 _tokenId) external view returns (uint256);
/// @notice Create a new license.
/// @dev Throws unless the NFT `_tokenId` exists. Throws unless the parent
/// license `_parentLicenseId` is active, or `_parentLicenseId` is a special
/// identifier not referring to any license (such as 0) and the NFT
/// `_tokenId` doesn't have a root license tethered to it. Throws unless the
/// message sender is eligible to create the license, i.e., either the
/// license to be created is a root license and `msg.sender` is the NFT owner,
/// or the license to be created is a sublicense and `msg.sender` is the holder
/// of the parent license.
/// @param _tokenId The identifier for the NFT the license is issued upon
/// @param _parentLicenseId The identifier for the parent license
/// @param _licenseHolder The address of the license holder
/// @param _uri The URI of the license terms
/// @param _revoker The revoker address
/// @return The identifier of the created license
function createLicense(uint256 _tokenId, uint256 _parentLicenseId, address _licenseHolder, string memory _uri, address _revoker) external returns (uint256);
/// @notice Revoke a license.
/// @dev Throws unless the license is active and the message sender is the
/// eligible revoker. This function should be used for revoking both root
/// licenses and sublicenses. Note that if a root license is revoked, the
/// NFT should be transferred back to its creator.
/// @param _licenseId The identifier for the queried license
function revokeLicense(uint256 _licenseId) external;
/// @notice Transfer a sublicense.
/// @dev Throws unless the sublicense is active and `msg.sender` is the license
/// holder. Note that the root license of an NFT should be tethered to and
/// transferred with the NFT. Whenever an NFT is transferred by calling the
/// ERC721 `transfer` function, the holder of the root license should be
/// changed to the new NFT owner.
/// @param _licenseId The identifier for the queried license
/// @param _licenseHolder The new license holder
function transferSublicense(uint256 _licenseId, address _licenseHolder) external;
}
イベント
-
CreateLicense
- ライセンスが作成されたときに発行されるイベント。
-
RevokeLicense
- ライセンスが取り消されたときに発行されるイベント。
-
TransferLicense
- ライセンスの所有者が変更されたときに発行されるイベント。
関数
-
isLicenseActive
- 指定したライセンスが現在有効かどうかを返す関数。
-
getLicenseTokenId
- ライセンスが紐づくNFTのトークンIDを取得する関数。
-
getParentLicenseId
- 親ライセンスのIDを取得する関数。
-
getLicenseHolder
- ライセンスの保有者のアドレスを返す関数。
-
getLicenseURI
- ライセンス内容が記されたURIを返す関数。
-
getLicenseRevoker
- そのライセンスを取り消す権限を持つアドレスを返す関数。
-
getLicenseIdByTokenId
- 指定したNFTに紐づくルートライセンスのIDを返す関数。
ライセンス操作関数
-
createLicense
- 新しいライセンスを作成する関数。
- ルートライセンスもサブライセンスもこの関数から発行されます。
-
revokeLicense
- 指定したライセンスを取り消す関数。
- NFTを作成者に返すことが推奨されます。
-
transferSublicense
- サブライセンスの譲渡を行う関数。
- ルートライセンスはNFT自体の
transfer
とともに自動で移動します。
ライセンス構造
引用: https://eips.ethereum.org/EIPS/eip-5218
ライセンスはツリー構造で管理されます。
NFTに1つのルートライセンス(NFT所有者のライセンス)が存在し、その所有者は他人にサブライセンスを発行できます。
さらにそのサブライセンスの保有者が、再度別の人にライセンスを出すことも可能です。
このように、権利関係が階層的に広がっていくイメージです。
ログによるトレース
全てのライセンス作成・譲渡・取消は、イベントログで追跡可能である必要があります。
ただし、取消時のログについては、以下のように柔軟な実装が許容されています。
- サブライセンスをまとめて一括で無効化すると、親ライセンスのみ(1件)の
Revoke
イベントを発行する。 - 全てのライセンスに対して個別に
Revoke
イベントを発行して、状態の確認がをしやすくする。
どちらを選ぶかは、実装者がライセンス仕様に応じて判断する設計になっています。
revokerの設計
ライセンスを取り消せるのは以下のいずれかです。
- 発行者(ライセンサー)
- 保有者(ライセンシー)
- 条件を満たしたときに
revokeLicense
を呼び出すスマートコントラクト
アドレスのハードコードは避け、譲渡などに対応できる柔軟な設計が推奨されています。
ライセンスURIとそのフォーマット
getLicenseURI
で返されるURIは、以下のような三層構造のJSON形式にすることが推奨されています。
{
"title": "License Metadata",
"type": "object",
"properties": {
"legal-code": {
"type": "string",
"description": "The legal code of the license."
},
"human-readable": {
"type": "string",
"description": "The human readable license deed."
},
"machine-readable": {
"type": "string",
"description": "The machine readable code of the license that can be recognized by software, such as CC REL."
}
}
}
この形式は、Creative Commonsのライセンス構造を参考にしており、以下の3種類の情報を分けて記述します。
- 法的に有効な文書
- ユーザー向けにわかりやすくまとめた説明文
- ソフトウェアが解釈できる機械判読用データ
URIの保存場所と更新について
ライセンスURIは基本的に変更できないことが前提となっています。
そのため、IPFSなどの分散型ストレージへの保存や、IPFSスタイルのURI(ハッシュが含まれ整合性を検証可能)を使うことが望ましいです。
どうしても更新が必要な場合は以下の方法をとります。
- 元のライセンスを取り消して新しいものを作り直す。
- ライセンスを更新する関数を追加する(ただし実装者はその呼び出し権限を厳密に制御する必要があります)。
supportsInterface
の要件
このインターフェースに対応していることを確認するため、ERC165のsupportsInterface
関数で 0xac7b5ca9
を受け取ったときは必ず true
を返す必要があります。
ERC165については以下の記事を参考にしてください。
補足
ERC5218の意図と設計思想
ERC5218は、NFTに関連する全てのライセンス情報を追跡可能にすることで、権利管理をしやすくすることを目的としています。
なぜ既存のERC721では不十分なのか?
ERC721はNFTの所有者を記録する規格ですが、法律上の利用権(ライセンス)までは記録していません。
オプションのメタデータ機能を使ってライセンス情報を載せることもできますが、サブライセンス(再許諾)の関係性までは追跡できないためWeb3が掲げる「透明性」は不十分です。
また、回避策として「特定のライセンスを表すNFTを発行する」といった事例(例:BAYC #6068のロイヤリティフリーライセンス)もありますが、
これはライセンス同士の関係が曖昧になりやすく、監査の時にすべてのNFTを調べて非標準のメタデータまで確認する必要があるという問題があります。
どう解決するのか?
ERC5218では、全てのライセンスをツリー構造(親子関係)で記録します。
こうすることで、以下のようなメリットがあります。
- ルートライセンスとサブライセンスの関係性が明確
- イベントログからライセンスの発行・譲渡・取り消しを一貫して追跡可能
- ERC721との互換性を保ちつつ、ライセンス管理を強化できる
著作権譲渡とは異なる扱い
ERC5218は、NFTに著作権の利用許諾(ライセンス)を紐づけることを目的としています。
法的に厳格な「著作権そのものの譲渡」ではないため、著作権者の明示的な署名までは不要です。
ただし、もし本当に著作権そのものをNFTと一緒に譲渡したい場合は、ERC5289との組み合わせが想定されています。
ERC5289については以下の記事を参考にしてください。
ERC5289では法的契約の署名をオンチェーンで扱う仕組みが提供されており、以下のような処理をコントラクトでアトミック(両方成功するか両方失敗するかの形で)な形で実行できます。
- ERC5289を使って法的契約書への署名を記録
- ERC5218を使ってライセンス付きNFTを譲渡
このようにすれば、NFTと一緒に法的権利も確実に移転できます。
互換性
ERC5218は、ERC721と互換性があります。
参考実装
ライセンス構造体
struct License {
bool active; // このライセンスが有効かどうか
uint256 tokenId; // 紐づくNFTのトークンID
uint256 parentLicenseId; // 親ライセンスのID(サブライセンスの場合)
address licenseHolder; // ライセンスの保有者
string uri; // ライセンス内容のURI
address revoker; // 取消権限を持つアドレス
}
ライセンス管理用のマッピング
-
_licenses
- key: ライセンスID
- value: ライセンス情報
-
_licenseIds
- key: NFTのトークンID
- value: ルートライセンスのID
これにより、NFTごとにツリー構造のライセンスが紐づき、各ライセンスから親ライセンスをたどることでルートまでさかのぼれるようになっています。
ライセンスの取り消しとサブライセンスの扱い
ライセンスを取り消すと、その下にあるすべてのサブライセンスも無効になる設計です。
ただし、ガスコスト削減のために「lazy方式(遅延評価)」を採用しています。
つまり、明示的にrevokeLicense
を呼び出されたライセンスにしかactive = false
は設定されません。
その代わり、isLicenseActive()
関数では「親ライセンスがすべて有効であること」も確認するようになっています。
サブライセンスの制約
ルートライセンスではないライセンス(=サブライセンス)については、以下のような制約があります。
- 有効なライセンスを持つ人だけが、新しいサブライセンスを発行できる。
- 有効なライセンスを持つ人だけが、それを他の人に譲渡できる。
- 有効なライセンスに対してだけ、取消権限を持つ人が取り消しを実行できる。
ルートライセンスとERC721との連携
ルートライセンスはNFの動きと連動します。
- NFTがMintされた時、その所有者にルートライセンスが発行される。
- NFTが
transfer
された時、ライセンス保有者も新しいNFT所有者に切り替わる。 - NFTのルートライセンスが取り消される時、NFTは元のクリエイターに戻される。
- その後、クリエイターが新しいライセンスと共に再度NFTを他人に譲渡できる。
この設計により、ライセンスとNFTの所有権がずれることなく一緒に扱えるようになっています。
Token-Bound NFT License との関係
ERC5218に準拠した実装と特に相性が良いのが「Token-Bound NFT License(TBNL)」です。
これは、ERC5218のインターフェースに最適化されており、ライセンスの文言をどのように記述するかについての具体例も示しています。
つまり、ライセンスを技術的にどう管理するかだけでなく、法律的な内容をどう記述するかの参照にもなるということです。
セキュリティ
ERC5218を実装する時には、ライセンス保有者(licenseHolder
)や取消権限者(revoker
)に対する権限の扱いに十分注意する必要があります。
ライセンス保有者と取消権限者の制御
以下のような点に注意が必要です。
- ライセンスが他人に譲渡される可能性がある場合
- ライセンスを取り消す権限をコントラクト側に持たせる場合
このようなケースで、revoker
コントラクトの中に特定のlicenseHolder
アドレスをハードコードするのは危険です。
なぜなら、ライセンスが譲渡されたあともその古いアドレスが使われ続けてしまうと、想定外の挙動やセキュリティ上の問題が発生する可能性があるからです。
柔軟で安全な設計
そのため、ライセンス管理ロジックはできるだけ柔軟かつ更新に強い形で実装することが推奨されます。
例えば、現在のlicenseHolder
を毎回コントラクト内で動的に参照するようにする、または所有者の更新に対応できるように設計されたアクセス制御機構を導入するといった対応が考えられます。
引用
James Grimmelmann (@grimmelm), Yan Ji (@iseriohn), Tyler Kell (@relyt29), "ERC-5218: NFT Rights Management [DRAFT]," Ethereum Improvement Proposals, no. 5218, July 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5218.
最後に
今回は「NFTに関するライセンスをオンチェーンで管理する仕組みを提案しているERC5218」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!