🦊

VERIFY TOOLとは何か?

2024/02/28に公開

挨拶

こんにちは、T4D4と申します。今回は少し前にTwitterで見つけて気になっていたVerifyというサービスについて調べてみました。

サービス概要

Verifyとは、米大手メディアFOX社がディープフェイクに対抗し、コンテンツの出所を保証する為、Polygon POS上に構築したサービスです。
Verifyはコンテンツのライセンスと出所の中央レポジトリとなることを目指しており、コンテンツの出所を保証するためにコンテンツのPublisherとSignatureを登録し、それを元にコンテンツの出所を確認できます。

特徴

  • トレーサビリティー
    コンテンツがVerifyに追加されると、秘密鍵で署名されます。この署名により、各コンテンツは特定の組織または個人と結びつけられます。
    この署名はコンテンツに関連する付加的なメタデータだけでなく、アセット自体の所有権も証明します。これはメタデータの真偽やコンテンツのパブリッシャーからの所有権を検証するものではないですが、両者を登録することでコンテンツ・メタデータの公開論争や、時間の経過と共に悪質な行為をする人の特定が可能になります。
  • コンテンツ・メタデータ・レジストリ
    Verifyで公開される各コンテンツには、メタデータが関連付けられています。このメタデータは、発行者の署名を通じてデジタルアセットに紐付けられています。
    パブリッシャーはデジタルアセットの作成に関する追加情報を提供できます。例を挙げると、画像の公開者はその被写体の正当性を証明するために写真を撮影したデバイス、撮影場所、身元に関する情報を提供できます。
  • アクセスとライセンスのシングルソース
    Verifyは、スマートコントラクトライセンスでデジタルアセットをバインドします。これらのプログラム可能なライセンスにより、発行者はデジタルコンテンツへのアクセスと参照のルールを宣言できます。
    パブリッシャーは、アセットを一般がアクセス可能な場所に保管しなければなりません。ライセンスされたコンテンツの場合、コンテンツは暗号化された形で保存できます。コンテンツを複合化するための鍵は、スマートコントラクトのライセンスを指す鍵管理システムに保存され、アセットを複合化するための対象鍵、またはアセット自体の平文バージョンが配布される。
    これにより、スマートコントラクトで指定されたルールや要件を満たすことで、コンシュマーによるコンテンツの無許可アクセス/参照が可能になります。
    ※追記:ドキュメントの直訳を載せましたがコードを読んだところ今(2024/03/04)のところコード上でのライセンス管理はされていないようです。

実際に試してみる

まず、Verifyにアクセスすると、コンテンツ・メタデータ・レジストリ以下のような画面が表示されます。
Verify Top Page
その後、左上の入力フォームにURLを入力し右矢印ボタンをクリックすると、その入力したURLのコンテンツの出所を確認できます。
たとえば、次のFOXの記事のURLを入力すると以下のような結果が表示されます。
使用した記事:Texas governor pushes back on Karine Jean-Pierre, says state will win in courts | Fox News

Verify Result Match
今度は、Zennの僕の過去の記事(=未登録の記事)で試してみます。
使用した記事:Cloudflareについて調べてみた
Verify Result No match
このように、登録済みの記事ではPublisherやSignatureなどの情報が表示されますが、未登録の記事ではNo matchとだけ表示されます。

仕組みを調べてみる

Verifyの公式ドキュメントによると、Verifyには、IdentityRegistry.solと、ContentGraph.solという2つのスマートコントラクトがあります。

IdentityRegistry.sol
ContentGraph.sol

IdentityRegistry.sol

まずは、IdentityRegistry.solについて見ていきます。
公式ドキュメントによると、IdentityRegistry.solは、スマートコントラクトのインターフェイスで、オフチェーンメッセージを安全な使用のためにEIP712[1]を利用しています。
署名の有効期限とノンスは署名のリプレイ攻撃を防ぐために使用されます。
IdentityRegistry.solには次のような関数を持っています。

関数名(引数) 内部処理・戻り値
registered(address user) アドレスがレジストリのルートIDとして登録されていればtrueを返し、そうでなければfalseを返す。
rootName(address user) ルートアドレスを所有する ID に対応する名前を返します。登録されていない場合は空文字列:"".を返します
nameToRoot(string memory name) 登録されていれば、渡された名前に対応するルート ID アドレスを返し、そうでなければゼロアドレスを返す。
nonces(address user) 署名ウォレットの nonce 値を返します。中間 ID の登録や登録解除の際に、署名のリプレイを防ぐために使用します。
used(address user) 署名キー・ペアに対応するアドレスが登録されていればtrueを返し、そうでなければfalseを返す。ID は一度しか登録できません。
registerRoot(address root, string memory name) 渡されたルートIDと名前をVERIFY ID レジストリに登録する。このスマートコントラクトの所有者のみ呼び出し可能。例外処理:InvalidParams()、AlreadyRegistered()を使用する
unregisterRoot(address root) VERIFY ID レジストリからルートIDとその名前の登録を解除する。このスマートコントラクトの所有者のみ呼び出し可能。
register(bytes memory signature, address root, address intermediate, uint256 expiry, uint256 chainID, uint256 deadline) すでに登録されているルートID配下の中間IDを登録する。有効期限は、中間IDにコンテンツの登録を許可する有効期限。chainIDは、環境間で署名がリプレイ攻撃されるのを防ぐために使用される。このメソッドは、有効期限が切れていない限り、有効期限を延長または短縮するために呼びだすことができる。このメソッドはルートIDの所有者のみ呼び出せる。例外処理:SignatureExpired()、AlreadyRegistered()、RegistryExpired()、InvalidSignature()を使用する
unregister(bytes memory signature, address root, address intermediate, uint256 chainID, uint256 deadline) ルートID配下の中間IDの登録を解除する。このメソッドはルートIDの所有者のみ呼び出せる。例外処理:NotRegistered()、InvalidParams()、SignatureExpired()、InvalidSignature()を使用する
whoIs(address identity) ルートが登録されており、中間 ID が署名付きで登録されており、署名がまだ有効である という関係が存在する場合、渡された中間 ID アドレスの対応するルート ID を返す。Address(0)は、関連が存在しないことを意味する。
エラー系
AlreadyRegistered() すでに登録されている ID を再利用しようとするとスローされます。
InvalidParams() 渡されたパラメーターがメソッドの仕様に基づき無効な場合にスローされます。
InvalidSignature() 回収された署名者が宣言された署名者と一致しない場合にスローされます。
NotRegistered() アドレスが登録されていない場合にスローされる。
RegistryExpired() ルートへの中間IDの登録を維持する署名の有効期限が切れ、有効でなくなった場合にスローされる。
SignatureExpired() 使用中の署名が作成時に指定された期限を過ぎた場合にスローされる。

ContentGraph.sol

次に、ContentGraph.solについて見ていきます。
公式ドキュメントによると、ContentGraph.solは、コンテンツグラフスマートコントラクトのインターフェイスです。このコントラクトはERC6150[2]を利用し、そのインターフェイスをサポートしています。
ContentGraphは、一意のIDを使用してすべてのコンテンツにインデックスを付けます。IDは、アセット・ノードの場合、基礎となるコンテンツに基づいています。参照ノードとorgノードについては、パブリッシャー次第です。例を挙げると以下のようになります。
Asset Ids: keccak256(binary data of content)
Organization/Reference Ids: Example method: keccak256(abi.encodedPacked(wallet,nodesCreated++))
ContentGraph.solには次のような関数を持っています。

関数名(引数) 内部処理・戻り値
totalSupply() コンテンツグラフ内のノードの総数を返します。
nodesCreated(address user) アドレスによって作成されたノードの数を返します。また、非アセットノードの ID を作成するための nonce として使用されます。
publishBulk(bytes32 parentId, ContentNode[] calldata content) NodeType ORGの親IDが与えられた場合、Contentノード(NodeType == REFERENCE|ASSET)のセットを発行し、assetsノードにはuriを、参照ノードにはreferenceOfを設定する。詳細は長いので表下部に記載。
publish(bytes32 parentId, ContentNode content) NodeType ORG の親 ID が与えられると、Content ノード (NodeType == REFERENCE|ASSET) を発行し、uri をアセットノードに、referenceOf を参照ノードに設定する。詳細は長いので表下部に記載。
createNode(bytes32 id, bytes32 parentId, NodeType nodeType, bytes32 referenceOf) NodeType ORG の親 ID が与えられると、指定されたタイプのノードを作成する。NodeType が REFERENCE の場合、referenceOf 値も設定する
move(bytes32 id, bytes32 newParentId) 渡されたidに対応するノードを、渡されたnewParentIdに対応するorgノードに移動する。詳細は長いので表下部に記載。
setAccessAuth(bytes32 id, address accessAuth) id のノードのアクセス認可契約を設定します。詳細は長いので表下部に記載。
setReferenceAuth(bytes32 id, address referenceAuth) id のノードの参照認証契約を設定します。詳細は長いので表下部に記載。
setURI(bytes32 id, string calldata uri) idにトークン/ノードのURIを設定します。詳細は長いので表下部に記載。
auth(bytes32 id, address user) 渡されたユーザアドレスが、idに格納されているコンテンツへのアクセスを許可されているかどうかを返します。詳細は長いので表下部に記載。
refAuth(id, user) 渡されたユーザアドレスが、コレクション内の id に格納されているコンテンツを参照する権限があるかどうかを返します。詳細は長いので表下部に記載。
getNode(bytes32 id) 渡された ID に対応するノードが存在する場合に、そのノードを返します。NodeType REFERENCE のノードは uri を持たないので、参照ノードの返される uri は参照するアセットノードの uri となります。詳細は長いので表下部に記載。
tokenToNode(uint256 token) ERC6150から存在するtokenIdが与えられると、その与えられたtokenIdに対応するNodeを返します。
イベント系 ドキュメントに詳細な記載ナシ
Initialized(version)
Moved(id, from, to)
AccessAuthUpdate(id, accessAuthContract)
ReferenceAuthUpdate(id, refAuthContract)
URIUpdate(id, uri)
エラー系
NotAuthorized() 発信者アドレスがIdentityレジストリのルートIDに登録されていない場合にスローされる。
InvalidParams() 渡されたパラメーターが無効な場合にスローされます。
NodeDoesNotExist() まだ作成されていないノードに対して操作を実行しようとすると、クラウンが発生する。
NodeAlreadyExists() すでに存在する ID を使用してノードを作成しようとするとスローされます。

長くて詳細を後回しにした関数の詳細

  • publishBulk(bytes32 parentId, ContentNode[] calldata content)
    • NodeType ORGの親IDが与えられた場合、Contentノード(NodeType == REFERENCE|ASSET)のセットを発行し、assetsノードにはuriを、参照ノードにはreferenceOfを設定する。これは、以下の限り成功する。
      • 渡された親は、中間が代行するルートIDが所有する既存のorgノードです。
      • すべてのIDは、そのタイプに基づいて正しく形成され、一意であり、コンテンツグラフにすでに存在しない。
      • nodeType ASSETのContentNodesには、uriを渡さなければならない。
      • nodeType REFERENCEのContentNodesの場合、referenceOfの値はグラフ内の既存のアセット・ノードでなければならない。
        struct ContentNode {
        bytes32 id;
        NodeType nodeType;
        bytes32 referenceOf;
        string uri;
        }
        
    • 例外処理:NotAuthorized()、InvalidParams()、NodeAlreadyExists()を使用する
  • publish(bytes32 parentId, ContentNode content)
    • NodeType ORGの親IDが与えられると、Contentノード (NodeType == REFERENCE|ASSET) を発行し、uriをアセットノードに、referenceOfを参照ノードに設定する。これは、以下の限り成功する。
      • 渡された親は、中間が代行するルートIDが所有する既存のorgノードです。
      • idはそのタイプに基づいて正しく形成され、一意。
      • nodeType ASSETのContentNodeには、uriを渡さなければならない。
      • nodeType REFERENCEのContentNodeの場合、referenceOfの値はグラフ内の既存のアセット・ノードでなければならない。
        struct ContentNode {
        bytes32 id;
        NodeType nodeType;
        bytes32 referenceOf;
        string uri;
        }
        
    • 例外処理:NotAuthorized()、InvalidParams()、NodeAlreadyExists()を使用する
  • createNode(bytes32 id, bytes32 parentId, NodeType nodeType, bytes32 referenceOf)
    • NodeType ORGの親IDが与えられると、指定されたタイプのノードを作成する。NodeTypeがREFERENCEの場合、referenceOf値も設定するこれは以下の限り成功する。
      • 渡された親は、中間が代行するルートIDが所有する既存のorgノードです。
      • idはそのタイプに基づいて正しく形成され、一意。
      • nodeType REFERENCEのContentNodeの場合、referenceOfの値はグラフ内の既存のアセット・ノードでなければならない。
    • 例外処理:NotAuthorized()、InvalidParams()、NodeAlreadyExists()を使用する
  • move(bytes32 id, bytes32 newParentId)
    • 渡されたidに対応するノードを、渡されたnewParentIdに対応するorgノードに移動する。これは、以下の限り成功する。
      • 渡されるidは、中間IDが代行するルートIDが所有する既存のノードです。
      • 渡されるnewParentIdは、中間が代行するルートIDが所有する既存のorgノードです。
    • 例外処理:NotAuthorized()、NodeDoesNotExist()を使用する
  • setAccessAuth(bytes32 id, address accessAuth)
    • idのノードのアクセス認可契約を設定する。これは以下の限り成功する。
      • 渡されたidに対応するノードは、中間が代行するルートIDが所有する。
    • 例外処理:NotAuthorized()、NodeDoesNotExist()を使用する
  • setReferenceAuth(bytes32 id, address referenceAuth)
    • idのノードの参照認可契約を設定する。これは以下の限り成功する。
      • 渡されたidに対応するノードは、中間が代行するルートIDが所有する。
      • ノードは、NodeType REFERENCEではない。
    • 例外処理:NotAuthorized()、NodeDoesNotExist()を使用する
  • setURI(bytes32 id, string calldata uri)
    • idにトークン/ノードのURIを設定する。これは以下の限り成功する。
      • 渡されたidに対応するノードは、中間が代行するルートIDが所有する。
      • ノードは、NodeType ASSETではない。
    • 例外処理:NotAuthorized()、NodeDoesNotExist()を使用する
  • auth(bytes32 id, address user)
    • 渡されたユーザアドレスが、idに格納されているコンテンツへのアクセスを許可されているかどうかを返します。
      • authが設定されていない場合、またはユーザーがノードのレベルでauthに失敗した場合、アクセスauthはそのノードの親orgノードに委譲されます。より低いレベルでユーザが拒否された場合、authモジュールが設定されていないルート・ノードにはauthが委譲されません。
      • アクセスされるノードが参照ノードの場合、参照トークンの所有者は、ユーザが参照ノードのアクセス認証を満たすことに加えて、参照されるアセットの参照認証を満たす必要があります。
    • 例外処理:NodeDoesNotExist()を使用する
  • refAuth(id, user)
    • 渡されたユーザアドレスが、コレクション内のidに格納されているコンテンツを参照する権限があるかどうかを返します。
      • authが設定されていない場合、またはユーザーがノードのレベルでauthに失敗した場合、参照authはそのノードの親orgノードに委譲されます。より低いレベルでユーザが拒否された場合、authモジュールが設定されていないルート・ノードにはauthが委譲されません。
      • 参照認証は、REFERENCEタイプのノードでは常に失敗する。
    • 例外処理:NodeDoesNotExist()を使用する
  • getNode(bytes32 id)
    • 渡されたIDに対応するノードが存在する場合に、そのノードを返します。NodeType REFERENCEのノードはuriを持たないので、参照ノードの返されるuriは参照するアセットノードのuriとなります。
        struct Node {
          uint256 token;
          NodeType nodeType;
          bytes32 id;
          bytes32 referenceOf;
          string uri;
          address accessAuth;
          address referenceAuth;
      }
    
    • 例外処理:NodeDoesNotExist()を使用する

おわりに

今回は、米大手メディアFOX社がディープフェイクに対抗し、コンテンツの出所を保証する為、Polygon POS上に構築したサービスVerifyについて調べてみました。細かい実装まで理解するのは大変でしたが、概要は掴めたと思います。今後も、僕の興味次第で色々なサービスや技術についてリサーチしていきたいと思います。誤字脱字や誤った情報があれば、連絡いただけると嬉しいです。

参考

脚注
  1. EIP712は型付き構造化データのハッシュと署名のための標準です。詳細はEIP712を参照してください。 ↩︎

  2. ERC6150とはEIP721の拡張規格です。多層ファイルシステムのような階層型NFTを提案しています。この標準は、親NFTや子NFT、NFTがリーフノードかルートノードかを取得するためのインターフェイスを提供し、NFT間の階層関係を維持します。詳細はERC6150を参照してください。 ↩︎

Discussion