はじめに
前回の続きです。今回は、ContentGraph.solをICP上のRustに変換します。
ContentGraph.solとは
ContentGraph.solは割と複雑なので、まずはドキュメントをperplexity.aiに翻訳させます。
ドキュメント
コンテンツグラフは、階層的なNFTのためのERC6150標準に基づいています。この階層構造により、子から親へのライセンスの継承が可能になります。グラフには3種類のトークン/ノードが存在します。
- 組織ノード: コンテンツの組織化を管理するために使用されます
- 参照ノード: 他のパブリッシャーが所有するアセットノードへの参照
- アセットノード: デジタルアセットを抽象的に表すノード
グラフのルート(id: 0)は誰にも所有されていません。これにより、VERIFYのIDレジストリを通じて登録されたパブリッシャーは誰でも、子としてノードを作成することができます。これらのノードタイプにより、VERIFYに公開されたコンテンツは、パブリッシャーの組織的およびコンテンツのライセンスニーズに基づいて、複数のパターンの認証/所有権を持つことができます。
組織(Org)ノード
Orgノードはコンテンツを直接参照しません。その目的は、参照ノードとアセットノードで表されるコンテンツを整理することです。これにより、Orgノードのすべての子は、そのレベルで指定されたアクセスまたは参照ライセンスを継承することができます。Orgノードの直接の子ノードは、Orgノードにすることもできます。Orgノードの所有者は以下のことができます。
- 任意のタイプの新しい子ノードを作成する
- 任意のタイプの子ノードを削除する
- 任意のタイプの子ノードを編集する
- Orgノードまたは任意の子のアクセスまたは参照ライセンスを設定する
- Orgまたは任意の子トークンを他のIDに転送する
参照ノード
参照ノードには子ノードがなく、グラフの葉としてのみ存在できます。また、グラフ内の他のノードから参照されることはできません。参照ノードの所有者は以下のことができます。
- 参照ノードのアクセスモジュールを設定する
- 参照ノードを削除する
- ノードを他のIDに転送するか、所有する親間で移動する
auth()メソッドを通じて参照ノードにアクセスするためには、呼び出すユーザーは作成者が設定したアクセス認証を満たし、作成者は参照先のアセットノードに設定された参照認証モジュールを満たす必要があります。
アセットノード
アセットノードは抽象的にデジタルアセットを表します。アセットノードは、アセットのバイナリデータを保存するためのストレージプロバイダーのURIと、アセットのメタデータを含むJSONLDドキュメントを指定します。アセットノードの所有者は以下のことができます。
- アセットノードのアクセスモジュールを設定する
- アセットノードの参照モジュールを設定する
- アセットノードを削除する
- ノードを他のIDに転送するか、所有する親間で移動する
アセットノードのauth()メソッドを通じてアクセスするには、呼び出し元のユーザーが作成者が設定したアクセス認証を満たす必要があります。
アセットノードのreference()メソッドを通じて参照するには、呼び出し元のユーザーが作成者が設定した参照認証を満たす必要があります。参照認証は、他のパブリッシャーがアセットを参照できるようにするために使用されます。
組織ノード
組織ノードID
組織ノードはデジタルアセットを直接指し示さないため、そのIDは表現するコンテンツに基づいて計算されません。
組織IDは一意である必要があり、その1つの方法は以下のように計算することです。
newOrgNodeId = keccak256(abi.encodePacked(address(user),nonce + 1));
認証
参照とアクセスの両方の認証モジュールは、setAccessAuth()とsetReferenceAuth()メソッドを使用して設定されます。組織レベルでユーザーに認証を提供することは、ここで認証されたすべてのユーザーがすべての子コンテンツノードに対してアクセス/参照を持つことを意味します。
メタデータ
各組織ノードには、インデックス作成ユーザーがその子のコンテンツを理解するのに役立つオプションのメタデータが関連付けられています。dataフィールドは、基礎となるコンテンツを利用するために必要な情報を組織が宣言するために柔軟性があります。将来的には、組織パターンの標準が開発される可能性があります。
メタデータスキーマフィールド
- data: 組織ノードに関連付けられたすべてのオプションのメタデータを含む。
- data.description: 組織ノードの子の説明文
- data.manifest: 追加のメタデータを保存し、署名者によって証明できる拡張可能なフィールド
- signature: dataフィールドのコンテンツに署名することで生成された署名を含む
- signature.curve: コンテンツの署名に使用された楕円曲線
- signature.signature: メッセージの署名から生成された署名
- signature.digest: dataフィールドのコンテンツのkeccak256ハッシュ
- signature.description: 署名の生成に使用された方法の説明文
組織ノードスキーマの例
{
"data": {
"description": "",
"manifest": {},
},
"signature": {
"curve": "",
"signature": "",
"message": "",
"description": "",
},
}
参照ノード
参照ノードは、既存のアセットノードへのリンクです。この関連付けは、ノード構造体の referencOf
にコンテンツグラフとして保存されます。参照ノードは、コンテンツグラフの葉としてのみ存在します。
ID
組織ノードと同様に、参照ノードのIDは、それらが表すコンテンツに基づいて計算されません。参照IDは一意である必要があります。これらのIDの作成方法はパブリッシャーに委ねられています。推奨される方法の1つは、パブリッシングウォレットのパブリックアドレスとグラフ内で作成されたトークンの数から計算することです。
newReferenceNodeId = keccak256(abi.encodePacked(address(user),nonce + 1));
認証
-
setAccessAuth()
メソッドを使用すると、参照ノードにアクセスするためのアクセス認証モジュールを設定できます。これにより、参照ノードの所有者は、どのユーザーが参照ノードにアクセスできるかを制御できます -
参照ノードは、他の参照ノードから参照されることはできません。したがって、参照ノードに対して参照アクセスモジュールを設定することはできません
-
ユーザーに参照の認証を提供するということは、以下の条件を満たす必要があることを意味します
- 参照ノードの所有者が、参照先のアセットノード上で設定された参照認証モジュールを満たしている場合、ユーザーは参照されているアセットにアクセスできるようにする必要があります
-
ユーザーが参照ノードに設定されたアクセスモジュールを満たしていない場合でも、参照ノードの所有者が参照先のアセットノードの参照モジュールを満たしている場合は、アクセスの認証は親の組織ノードのアクセスモジュールに委任されます
つまり、参照ノードへのアクセスは、参照ノードに設定されたアクセス認証モジュールと、参照先のアセットノードの参照認証モジュールの両方によって制御されます。参照ノードの所有者は、これらのモジュールを満たすことで、ユーザーに参照ノードとアセットノードへのアクセスを許可することができます。
メタデータ
参照ノードは追加のメタデータを提供しません。インデックス化された参照ノードのメタデータへのURIは、アセットノードのスキーマに従う元のアセットのメタデータを指します。
アセットノード
アセットノードは、アセットのメタデータを指し示し、トークン化された表現として機能します。アセットノードは、コンテンツグラフの葉としてのみ存在します。
アセットノードID
アセットの一意のIDは、それが表すコンテンツに基づいて計算されます。デジタルアセットの一意の識別子は、生データをkeccak256ハッシュアルゴリズムでハッシュ化することによって生成されます。これにより、256ビット/32バイトの一意の識別子が作成されます。アセットノードは一意であるため、デジタルアセットごとに1人の所有者しか存在しないはずです。既存のアセットは、元のアセットの参照ノードを作成することで再配布できます。
入力: Hello World!
keccak256出力: 0x3ea2f1d0abf3fc66cf29eebb70cbd4e7fe762ef8a09bcc06c8edf641230afec0
認証
参照とアクセスの両方の認証モジュールは、setAccessAuth()
と setReferenceAuth()
メソッドを使用して設定されます。アセットレベルでユーザーに認証を提供することは、ここで認証されたすべてのユーザーがすべての子コンテンツノードに対してアクセス/参照を持つことを意味します。ユーザーがいずれも満たさない場合、認証は親の組織ノードモジュールに委任されます。
アセットノードスキーマフィールド
-
data
: アセットに関連付けられたすべてのメタデータを含む-
data.description
: アセットの基礎となるコンテンツのテキストによる説明 -
data.encrypted
: コンテンツの場所におけるデータの状態を説明するブール値 -
data.contentBinding.algo
: アセットに関連付けられた生の暗号化されていないコンテンツをハッシュ化するために使用されるアルゴリズム
-
アクセスメソッドとスキーマ
ライセンスセクションで、アクセスの方法とVERIFYでのライセンスの仕組みについて詳しく説明しています。
署名
-
signature.curve
:data
フィールドのコンテンツの署名に使用される楕円曲線 -
signature.signature
: メッセージの署名から生成された署名 -
signature.message
:data
フィールドのコンテンツの keccak256 ハッシュ -
signature.description
: 署名の生成に使用された方法のテキストによる説明
Example Asset Node Schema
{
"data": {
"description": "",
"encrypted": true|false,
"access": {
},
"content": [
{
"location": "",
"type": ""
}
],
"manifest": {},
"contentBinding": {
"algo": "",
"hash": ""
},
},
"signature": {
"curve": "",
"signature": "",
"message": "",
"description": ""
},
}
ContentGraph.sol
ContentGraph.solは、コンテンツグラフのスマートコントラクトのインターフェースです。このコントラクトはERC6150を利用し、そのインターフェースをサポートしています。ここでサポートされている関数の詳細については、こちらをご覧ください。
ノードID
ContentGraphは、すべてのコンテンツを一意のIDを使用してインデックス化します。アセットノードの場合、IDは基礎となるコンテンツに基づいています。参照ノードと組織ノードの場合は、パブリッシャーに委ねられています。
- アセットID:
keccak256(コンテンツのバイナリデータ)
- 組織/参照ID: 例えば、
keccak256(abi.encodedPacked(wallet, nodesCreated++))
error
error NotAuthorized();
error InvalidParams();
error NodeDoesNotExist();
error NodeAlreadyExists();
enum
enum NodeType {
ORG,
REFERENCE,
ASSET
}
struct
struct ContentNode {
bytes32 id;
NodeType nodeType;
bytes32 referenceOf;
string uri;
}
struct Node {
uint256 token;
NodeType nodeType;
bytes32 id;
bytes32 referenceOf;
string uri;
address accessAuth;
address referenceAuth;
}
関数
totalSupply()
コンテンツグラフ内のノードの総数を返します。
nodesCreated(address user)
アドレスによって作成されたノードの数を返します。非アセットノードのIDを作成するためのnonceとして使用されます。
publishBulk(bytes32 parentId, ContentNode[] calldata content)
NodeTypeがORGの親IDが与えられた場合、一連のコンテンツノード(NodeType == REFERENCE | ASSET)を公開し、アセットノードのURIと参照ノードのreferenceOfを設定します。以下の条件を満たす場合に成功します:
- 渡された親は、中間者が代理として行動しているルートIDが所有する既存の組織ノードである必要があります
- すべてのIDは、そのタイプに基づいて正しく形成され、一意である必要があります。また、コンテンツグラフ内に既に存在していてはいけません
- nodeTypeがASSETのContentNodeの場合、uriを渡す必要があります
- nodeTypeがREFERENCEのContentNodeの場合、referenceOfの値は、グラフ内の既存のアセットノードである必要があります
struct ContentNode {
bytes32 id;
NodeType nodeType;
bytes32 referenceOf;
string uri;
}
次の例外をthrowします。
- NotAuthorized()
- InvalidParams()
- NodeAlreadyExists()
publish(bytes32 parentId, ContentNode content)
NodeTypeがORGの親IDが与えられた場合、コンテンツノード(NodeType == REFERENCE | ASSET)を公開し、アセットノードのURIと参照ノードのreferenceOfを設定します。以下の条件を満たす場合に成功します:
- 渡された親は、中間者が代理として行動しているルートIDが所有する既存の組織ノードである必要があります
- IDは、そのタイプに基づいて正しく形成され、一意である必要があります。また、コンテンツグラフ内に既に存在していてはいけません
- nodeTypeがASSETの場合、uriを渡す必要があります
- nodeTypeがREFERENCEの場合、referenceOfの値は、グラフ内の既存のアセットノードである必要があります
struct ContentNode {
bytes32 id;
NodeType nodeType;
bytes32 referenceOf;
string uri;
}
次の例外をthrowします。
- NotAuthorized()
- InvalidParams()
- NodeAlreadyExists()
createNode(bytes32 id, bytes32 parentId, NodeType nodeType, bytes32 referenceOf)
指定されたIDとプロパティを持つ新しいノードを作成します。以下の条件を満たす場合に成功します:
- 渡された親は、中間者が代理として行動しているルートIDが所有する既存の組織ノードである必要があります
- IDは、そのタイプに基づいて正しく形成され、一意である必要があります。また、コンテンツグラフ内に既に存在していてはいけません
- nodeTypeがREFERENCEの場合、referenceOfの値は、グラフ内の既存のアセットノードである必要があります
次の例外をthrowします。
- NotAuthorized()
- InvalidParams()
- NodeAlreadyExists()
move(bytes32 id, bytes32 newParentId)
指定されたIDのノードを、指定された新しい親IDの下に移動します。以下の条件を満たす場合に成功します:
- 渡されたIDは、中間者が代理として行動しているルートIDが所有する既存のノードである必要があります
- 渡された新しい親IDは、中間者が代理として行動しているルートIDが所有する既存の組織ノードである必要があります
- 移動されるノードは、現在の親の子である必要があります
- 新しい親は、移動されるノードのタイプを受け入れる必要があります(組織ノードのみが子を持つことができます)
次の例外をthrowします。
- NotAuthorized()
- NodeDoesNotExists()
setAccessAuth(bytes32 id, address accessAuth)
指定されたIDのノードのアクセス認証コントラクトを設定します。以下の条件を満たす場合に成功します:
- 渡されたIDに対応するノードは、中間者が代理として行動しているルートIDによって所有されている必要があります
次の例外をthrowします。
- NotAuthorized()
- NodeDoesNotExists()
setReferenceAuth(bytes32 id, address referenceAuth)
指定されたIDのノードの参照認証コントラクトを設定します。以下の条件を満たす場合に成功します:
- 渡されたIDに対応するノードは、中間者が代理として行動しているルートIDによって所有されている必要があります
- ノードはNodeType REFERENCEであってはいけません
次の例外をthrowします。
- NotAuthorized()
- NodeDoesNotExists()
setURI(bytes32 id, string calldata uri)
指定されたIDのトークン/ノードのURIを設定します。以下の条件を満たす場合に成功します:
- 渡されたIDに対応するノードは、中間者が代理として行動しているルートIDによって所有されている
- ノードはNodeType ASSETである
次の例外をthrowします。
- NotAuthorized()
- NodeDoesNotExists()
auth(bytes32 id, address user)
指定されたIDに格納されているコンテンツへのアクセスが、渡されたユーザーアドレスに対して認可されているかどうかを返します。
- 渡されたIDに対応するノードは、中間者が代理として行動しているルートIDによって所有されている必要があります
- ノードはNodeType REFERENCEであってはいけません
次の例外をthrowします。
- NodeDoesNotExists()
refAuth(bytes32 id, address user)
指定されたIDに格納されているコンテンツを参照することが、渡されたユーザーアドレスに対して認可されているかどうかを返します。
- 渡されたIDに対応するノードは、中間者が代理として行動しているルートIDによって所有されている必要があります
- ノードはNodeType ASSETである必要があります
参照認証が設定されていない場合、またはユーザーがノードレベルでの認証に失敗した場合、参照認証はノードの親の組織ノードに委任されます。
次の例外をthrowします。
- NodeDoesNotExists()
getNode(bytes32 id)
指定されたIDに対応するノードを返します。ノードが存在しない場合は例外をスローします。
struct Node {
bytes32 id;
bytes32 parentId;
NodeType nodeType;
bytes32 referenceOf;
address accessAuth;
address referenceAuth;
string uri;
}
次の例外をthrowします。
- NodeDoesNotExists()
tokenToNode(uint256 token)
ERC6150から存在するtokenIdが与えられた場合、そのtokenIdに対応するNodeを返します。
詳細はgetNode()
を参照してください。
次の例外をthrowします。
- NodeDoesNotExists()
Events
Initialized(version)
Moved(id, from, to)
AccessAuthUpdate(id, accessAuthContract)
ReferenceAuthUpdate(id, refAuthContract)
URIUpdate(id, uri)
Errors
ContentGraph.solは、以下のカスタムエラーを使用します。
NotAuthorized()
呼び出し元が操作を実行する権限を持っていない場合にスローされます。
InvalidNodeId()
無効なノードIDが渡された場合にスローされます。
NodeExists()
作成しようとしているノードがすでに存在する場合にスローされます。
InvalidNodeType()
無効なノードタイプが渡された場合にスローされます。
InvalidParent()
無効な親ノードが渡された場合にスローされます。
InvalidReferenceOf()
無効な参照先が渡された場合にスローされます。
NodeNotFound()
指定されたノードが見つからない場合にスローされます。
Unauthorized()
呼び出し元がコンテンツへのアクセスまたは参照を許可されていない場合にスローされます。