0
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?

[ERC7734] シンプルなDID標準によるID検証の仕組みを理解しよう!

Posted at

はじめに

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

今回は、DID(分散型アイデンティティ)を暗号学的ハッシュでシンプルかつ安全に検証し、プライバシーを守りながら分散型アプリケーションで活用できる仕組みを提案しているERC7734についてまとめていきます!

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

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

概要

ERC7734は、ブロックチェーン上での分散型アイデンティティ(Decentralized Identity, DID)検証のための新しい標準を導入する提案です。
特徴的なのは、複雑な仕組みではなく、暗号学的ハッシュを用いてアイデンティティの証明やイベントを表現し、透明性と追跡可能性を担保している点です。

また、既存のDIDソリューションにありがちな「情報の過剰なオンチェーン化」や「複雑すぎる設計」を避けるために、設計思想はあくまでシンプルさ・プライバシー・ユーザーの自己管理を重視しています。
これにより、開発者にとっては余計なオーバーヘッドを減らし、ユーザーにとってはスムーズに利用できる形での分散型アプリケーション(dApp)への統合を可能にします。

この仕組みでは、アイデンティティの構造は極めてミニマルに保たれており、詳細な本人確認や属性情報の管理はオフチェーンで行うという柔軟な運用を前提としています。
つまり、ブロックチェーンは「証明の存在や改ざん防止」を担い、実際のアイデンティティデータはユーザーやサービス提供者の手元で安全に扱うことを目指しています。

動機

従来の中央集権的なアイデンティティ認証システムには、以下のような問題点が存在していました。

  • 利用が煩雑であり、サービス提供者・利用者双方に負担が大きい
  • データベースへの不正アクセスや情報漏洩のリスクが高い
  • ユーザーが自分のアイデンティティ情報をコントロールできず、管理主体に依存せざるを得ない

このような課題に対し、既存の分散型ID(DID)ソリューションは一定の解決策を提示していますが、「多機能すぎることによる複雑さ」が障壁となり、開発者やユーザーの採用が進みにくいという側面がありました。

ERC7734はこれらの課題を踏まえて、以下の方向性を示しています。

  • シンプルな分散型標準の提供
    複雑さを排除し、基本的なアイデンティティ検証にフォーカスした仕組みを提案しています。
  • プライバシー保護
    アイデンティティに関する詳細データはチェーン上に保存せず、ハッシュ化された証明情報のみを利用することで、ユーザーのプライバシーを保護します。
  • 幅広い普及を目指す設計
    dAppや既存のサービスに簡単に組み込めるため、金融(DeFi)、ゲーム、SNSなど多様な分野での利用を想定しています。

仕様

このDID(Decentralized Identity Verification)標準は、ブロックチェーン上でユーザーのアイデンティティを検証するための、シンプルかつセキュアでプライバシーを保護する仕組みを提供します。

Identity Contract

ブロックチェーン上でアイデンティティ検証を管理する中核的なコントラクトです。
ユーザーの検証状態を保存し、検証イベントを安全かつ透明に扱う仕組みを提供します。

Verification Function

ユーザーは verifyIdentity 関数を通じて、オフチェーンの証明や第三者からの認証を基にした2つのハッシュを提出します。
コントラクトはそれらのハッシュを比較し、アイデンティティの検証状態を更新します。

入力パラメータは以下です。

  • identityHash
    ユーザーのアイデンティティを表す暗号学的ハッシュ。
  • verificationHash
    証明や認証に基づく暗号学的ハッシュ。

IdentityVerified Event

アイデンティティの検証が成功すると、IdentityVerified イベントが発行されます。
これにより、dAppの開発者やユーザーは検証状況を追跡できるようになります。

Identity Structure

アイデンティティは「ユニークなアドレス(公開鍵)」をベースにしたシンプルな構造で表現されます。
名前や年齢などの追加情報は必須ではなく、オフチェーンで管理されます。
これによりシンプルさを保ちながら柔軟に拡張可能です。

インターフェース

pragma solidity ^0.8.0;

interface IDecentralizedIdentity {
    struct Identity {
        address userAddress;
        bytes32 identityHash;
        bytes32[2] verificationHashes;
        bool isVerified;
        uint256 timestamp;
    }

    event IdentityCreated(address indexed userAddress, bytes32 identityHash, uint256 timestamp);
    event IdentityVerified(address indexed userAddress, bytes32[2] verificationHashes, uint256 timestamp);
    event IdentityRevoked(address indexed userAddress, uint256 timestamp);

    function createIdentity(bytes32 identityHash) external;
    function verifyIdentity(bytes32[2] calldata verificationHashes) external;
    function revokeIdentity() external;
    function getIdentity(address userAddress) external view returns (Identity memory);
}

Identity

struct Identity {
    address userAddress;
    bytes32 identityHash;
    bytes32[2] verificationHashes;
    bool isVerified;
    uint256 timestamp;
}

ユーザーの分散型アイデンティティを表す構造体。

ユーザーアドレスと、それに紐づくアイデンティティ情報(ハッシュ化されたデータ、検証ハッシュ、検証状態、作成時刻)を記録します。
シンプルな構造にすることで、オフチェーンの拡張性を保ちながらオンチェーンでの追跡可能性を担保します。

パラメータ

  • userAddress
    • ユーザーのEthereumアドレス。
  • identityHash
    • アイデンティティデータのハッシュ。
  • verificationHashes
    • 検証に使用される2つのハッシュ値。
  • isVerified
    • アイデンティティが検証済みかを示すブール値。
  • timestamp
    • アイデンティティ作成の時刻。

IdentityCreated

event IdentityCreated(address indexed userAddress, bytes32 identityHash, uint256 timestamp);

新しいアイデンティティが作成された時に発行されるイベント。

ユーザーが新しいアイデンティティを作成したことをネットワーク全体に通知します。
これにより、他のdAppやサービスは新規登録を検知できます。

パラメータ

  • userAddress
    • アイデンティティを作成したユーザーのアドレス。
  • identityHash
    • アイデンティティデータのハッシュ。
  • timestamp
    • 作成時刻。

IdentityVerified

event IdentityVerified(address indexed userAddress, bytes32[2] verificationHashes, uint256 timestamp);

アイデンティティが検証された時に発行されるイベント。

ユーザーのアイデンティティが有効に検証されたことを示します。
これにより、サービス提供者や他のユーザーは、そのアイデンティティが正しく確認されたことを信頼できます。

パラメータ

  • userAddress
    • 検証が行われたユーザーのアドレス。
  • verificationHashes
    • 検証に使われた2つのハッシュ。
  • timestamp
    • 検証完了の時刻。

IdentityRevoked

event IdentityRevoked(address indexed userAddress, uint256 timestamp);

アイデンティティが取り消された時に発行されるイベント。

ユーザーが自身のアイデンティティを無効化したことを表します。
セキュリティ上の理由やサービス利用停止のために用いられます。

パラメータ

  • userAddress
    • アイデンティティを取り消したユーザーのアドレス。
  • timestamp
    • 取り消しが行われた時刻。

createIdentity

function createIdentity(bytes32 identityHash) external;

新しい分散型アイデンティティを作成する関数。

ユーザーが自身のアイデンティティを登録する時に使用します。
指定したハッシュを基に、ブロックチェーン上にアイデンティティが記録されます。

引数

  • identityHash
    • アイデンティティデータのハッシュ。

verifyIdentity

function verifyIdentity(bytes32[2] calldata verificationHashes) external;

分散型アイデンティティを検証する関数。

ユーザーが外部証明や認証をもとに得た2つの検証ハッシュを提出することで、アイデンティティの有効性が確認されます。
検証が成功すると IdentityVerified イベントが発行されます。

引数

  • verificationHashes
    • アイデンティティ検証に使用する2つのハッシュ値。

revokeIdentity

function revokeIdentity() external;

分散型アイデンティティを取り消す関数。

ユーザーが自らの意思でアイデンティティを無効化する場合に利用します。
セキュリティ上の問題や利用停止時に有効です。

getIdentity

function getIdentity(address userAddress) external view returns (Identity memory);

指定したユーザーの分散型アイデンティティを取得する関数。

ユーザーアドレスを指定することで、そのユーザーに関連づけられたアイデンティティ構造体全体を取得します。
dAppやサービス提供者はこの情報を利用して検証状態を確認できます。

引数

  • userAddress
    • 対象となるユーザーのEthereumアドレス。

戻り値

  • Identity
    • 指定されたユーザーのアイデンティティ構造体。

補足

この設計は、アイデンティティそのものの情報をブロックチェーンに直接保存せず、暗号学的ハッシュで表現する方針をとります。
平文のまま扱わないため、万が一チェーン上の情報が参照されても、元の個人情報が復元されることはありません。
ハッシュは「存在の証明(proof of existence)」として機能し、改ざん耐性と追跡可能性を両立します。

verificationHashes は柔軟な検証メカニズムを許容するためのフィールドです。
ここには、実装者の要件に応じてオフチェーンの様々な証明(暗号チャレンジの応答、第三者のアテステーション、外部KYCのハッシュ結果など)を格納できます。
標準として「ハッシュの意味づけ」を固定せずに実装側に委ねることで、ユースケースの変化に追従できる適応性を確保しつつ、プライバシーとセキュリティを維持します。

さらに、IdentityCreatedIdentityVerifiedIdentityRevoked といったイベントを含めることで、透明性とトレーサビリティを担保します。
イベントログはdAppや外部の監査・分析基盤が検証の経緯を確認するための信頼できる情報源になります。

ハッシュで表現して、意味づけは実装で決める」、「ステート変更はイベントで可視化する」という2つの方法により、最小限のオンチェーンステートで現実の多様なID検証フローに合わせやすい構成になっています。

参考実装

以下は、提示されたインターフェースに沿った最小限のアイデンティティコントラクトの参考実装です。

pragma solidity ^0.8.0;

import "./IDecentralizedIdentity.sol";

contract DecentralizedIdentity is IDecentralizedIdentity {
    // Mapping to store identities by user address
    mapping(address => Identity) private identities;

    // Function to create a new decentralized identity for the caller.
    // Parameters:
    // - identityHash Hash of the identity data.
    function createIdentity(bytes32 identityHash) external override {
        // Ensure identity does not already exist
        require(identities[msg.sender].userAddress == address(0), "Identity already exists");

        // Create the identity for the caller
        identities[msg.sender] = Identity({
            userAddress: msg.sender,
            identityHash: identityHash,
            verificationHashes: [bytes32(0), bytes32(0)], // Initialize with empty hashes
            isVerified: false,
            timestamp: block.timestamp
        });

        // Emit event for the creation of a new identity
        emit IdentityCreated(msg.sender, identityHash, block.timestamp);
    }

    // Function to verify the decentralized identity for the caller.
    // Parameters:
    // - verificationHashes: Hashes used for verifying the identity.
    function verifyIdentity(bytes32[2] calldata verificationHashes) external override {
        // Ensure identity exists
        require(identities[msg.sender].userAddress != address(0), "Identity does not exist");

        // Update verification hashes and mark identity as verified
        identities[msg.sender].verificationHashes = verificationHashes;
        identities[msg.sender].isVerified = true;

        // Emit event for the verification of identity
        emit IdentityVerified(msg.sender, verificationHashes, block.timestamp);
    }

    // Function to revoke the decentralized identity for the caller.
    function revokeIdentity() external override {
        // Ensure identity exists
        require(identities[msg.sender].userAddress != address(0), "Identity does not exist");

        // Mark identity as not verified
        identities[msg.sender].isVerified = false;

        // Emit event for the revocation of identity
        emit IdentityRevoked(msg.sender, block.timestamp);
    }

    // Function to retrieve the decentralized identity for a given user address
    // Parameters:
    // - userAddress Ethereum address of the user.
    // Returns:
    // identity The decentralized identity struct.
    function getIdentity(address userAddress) external view override returns (Identity memory) {
        return identities[userAddress];
    }
}

参考実装は、ユーザーごとに1つの Identitymapping(address => Identity) に保持します。
作成・検証・取り消しの各操作は、自分自身(msg.sender)のIDにのみ行うことができ、すべてのステート変更はイベントで公開されます。

操作 前提条件 状態変更 生成イベント
createIdentity そのアドレスに既存IDがない。 Identity を新規作成し、isVerified=false で保存する。 IdentityCreated
verifyIdentity そのアドレスの Identity が存在する。 verificationHashes を更新し、isVerified=true にする。 IdentityVerified
revokeIdentity そのアドレスの Identity が存在する。 isVerified=false に戻す(ハッシュは保持)。 IdentityRevoked
getIdentity なし。 ステートを読み出すのみ。 なし

verificationHashes に与える値(どのような証明をハッシュに落とすか)は、オフチェーンのワークフローで決まります。
例えば「チャレンジ応答のハッシュ」と「アテスター署名のハッシュ」を2枠に割り当てる、といった運用が可能です。

参考:オフチェーンでのハッシュ生成イメージ

// 例:EIP-712のような「誰が・何に・いつ同意したか」を明確化する型付きデータに対し、
// 署名とハッシュを作るフロントエンド側の疑似コード(ethers.js想定)

import { keccak256, toUtf8Bytes, solidityPackedKeccak256 } from "ethers";

// アイデンティティ本体(PII)は絶対に送らず、ソルト付きでハッシュ化
const identityPlain = JSON.stringify({ name: "Alice", dob: "1990-01-01" });
const identitySalt  = crypto.getRandomValues(new Uint8Array(32));
const identityHash  = keccak256(
  new Uint8Array([...toUtf8Bytes(identityPlain), ...identitySalt])
);

// チャレンジ応答やアテステーションを、アドレス・チェーンID・有効期限・nonceと束ねてハッシュ化
const challenge    = "proof:email@domain.tld";
const nonce        = crypto.getRandomValues(new Uint8Array(16));
const expiresAt    = Math.floor(Date.now()/1000) + 15*60; // 15分有効

const verificationHashA = solidityPackedKeccak256(
  ["address","uint256","bytes32","bytes","uint256","bytes16"],
  [userAddress, chainId, identityHash, toUtf8Bytes(challenge), expiresAt, nonce]
);

// もう一方は、外部アテスターの署名やサーバー側ハッシュなどを格納
const verificationHashB = keccak256(toUtf8Bytes("attestor:example.org:signatureblob"));

// これら2つを verificationHashes に渡す
// verifyIdentity([verificationHashA, verificationHashB])

セキュリティ

ERC7734はミニマルなため、どのようにハッシュを作るか・検証するかは実装者の責任領域になります。

セキュアなハッシュ化(Secure Hashing)

アイデンティティや検証に使うハッシュは、衝突耐性前像困難性を満たす安全な手法で生成します。
オンチェーンではSolidityの keccak256 が標準となっています。
オフチェーンでも keccak256 相当で整合を取り、ソルトや**ドメイン分離(domain separation)**を行うと推測耐性が上がります。

  • 同一データに対しても環境が違えばハッシュが異なるよう、ソルトやコンテキスト(chainIduserAddress、用途を示すタグなど)を詰めてからハッシュ化するのが安全です。
  • ハッシュ対象のシリアライズ(JSONやABIエンコード)は順序や型のぶれが出ない方法に固定します。

リプレイ攻撃対策(Replay Attacks)

verificationHashes は他者に観測され得るため、一度使った証明を再利用されるリプレイ攻撃を防ぐ工夫が必要です。
実装では、以下のいずれか(または両方)を推奨します。

脅威 対策の例
旧い検証データの再提出 ハッシュに expiresAt を含め、期限切れを弾く。
メッセージの再送 ユーザーごとに usedNonce を記録し、同じ nonce を拒否する。
文脈のすり替え userAddresschainId・用途タグをハッシュに含める。

参考となるオンチェーン側の拡張も示します。

// 例:verifyIdentity 内で nonce を消費し、二重使用を拒否する拡張イメージ
mapping(address => mapping(bytes16 => bool)) private usedNonce;

function verifyIdentity(bytes32[2] calldata verificationHashes, bytes16 nonce, uint256 expiresAt) external {
    require(block.timestamp <= expiresAt, "expired");
    require(!usedNonce[msg.sender][nonce], "nonce used");
    require(identities[msg.sender].userAddress != address(0), "Identity does not exist");

    usedNonce[msg.sender][nonce] = true;
    identities[msg.sender].verificationHashes = verificationHashes;
    identities[msg.sender].isVerified = true;

    emit IdentityVerified(msg.sender, verificationHashes, block.timestamp);
}

実装の頑健性(Implementation Flexibility)

ERC7734では柔軟性を重んじるため、ハッシュの意味づけと検証ロジックは各プロジェクトで決めることになります。

  • アテスターの真正性
    外部アテスター発行のアテステーションは、EIP712型データへの署名などで署名者の検証を可能にします。
    ハッシュに署名者の公開鍵(あるいは識別子)を結び、検証時に必ず検証してください。

EIP712については以下の記事を参考にしてください。

  • フロントランニング耐性
    トランザクションを傍受・先回りされても、msg.sender のスコープに閉じているため、他者の検証を横取りされることは基本的にありません。
    それでもハッシュ生成時に userAddress を組み込むのは有効です。
  • プライバシー保護
    イベントには平文化情報を含めず、必ずハッシュ化済みの値のみを扱います。
    ハッシュの推測を避けるため、十分なソルトなどを使用してください。

EIP712で型付きデータに署名するイメージ

// 署名対象に userAddress / chainId / purpose / identityHash / nonce / expiresAt を含めることで
// 誰が・どのチェーンで・何の目的で・いつまで有効か、を一意に固定できます。
const typedData = {
  domain: { name: "DID", version: "1", chainId, verifyingContract: contractAddress },
  types: {
    Verify: [
      { name: "user", type: "address" },
      { name: "purpose", type: "string" },
      { name: "identityHash", type: "bytes32" },
      { name: "nonce", type: "bytes16" },
      { name: "expiresAt", type: "uint256" },
    ],
  },
  message: {
    user: userAddress,
    purpose: "did.verify",
    identityHash,
    nonce,
    expiresAt,
  },
};
// 署名し、そのハッシュを verificationHashes[0] に格納する運用などが可能

引用

Anushka Yadav (@64anushka) 64anushka@gmail.com, "ERC-7734: Decentralized Identity Verification (DID)," Ethereum Improvement Proposals, no. 7734, June 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7734.

最後に

今回は「DID(分散型アイデンティティ)を暗号学的ハッシュでシンプルかつ安全に検証し、プライバシーを守りながら分散型アプリケーションで活用できる仕組みを提案しているERC7734」についてまとめてきました!
いかがだったでしょうか?

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

Twitter @cardene777

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

0
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
0
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?