PS/SLの佐々木です。 今回はEthereumを使用して簡単なDID/VCを実現してみようと思います。 DID/VCとは DID/VCとは分散IDと検証可能なデジタル証明書のことを指します。 通常の認証とは異なり、認証(VCを検証)する際に発行元に問い合わせる必要がありません。 よって発行元がなくなってしまっても問題なく検証ができます。 また検証もDID Resolverを使用することで誰でも検証することができるため、特定の機関が発行した証明書(VC)を他サービスで検証することも容易です。 DIDとEthereumの関係性 ブロックチェーンの技術はDID/VCの文脈で相性が良くたびたび話題に出ますが、一体どこでブロックチェーンが使用されているのでしょうか? DID/VCの文脈でブロックチェーンが登場するときには検証用の公開鍵の保存先としてブロックチェーンが登場します。 例えば今回実装を紹介するEthereumでは EIP1056 という規格で規定されています。 よってVCの中身の情報であったり実際の検証に関してはブロックチェーンが登場することはありません。 ではなぜブロックチェーンと相性が良いのでしょうか? それはどちらも分散型である点が挙げられます。 例えばdid:webの場合特定のドメインに依存する形でDIDを解決をする必要があります。一方でdid:ethrを使用する場合には分散ネットワーク上にあるため世界中のどこの誰であってもアクセスすることができるためDID/VCの世界観により近いと言えます。 DID/VCの実装にあたって必要な予備知識 今回はタイトル通りdid:ethrを使用してDID/VCを実現します。 Ethereumで実現するためにはEIP1056というDIDを管理する標準的な仕様があります。 ERC1056は、W3Cが提案するDID仕様と互換性があり、Ethereumエコシステムの中で自己主権型IDを管理するための標準的な手段となっています。 このERC1056を実装するためのライブラリ、コントラクトとして以下のようなものが提供されており、今回はこれらに倣って実装していきます。 ethr-did-registry 公開鍵を保存したり、did-documentに表示される属性を更新できるようにするEthereumのコントラクトコードです。 これらは自分でデプロイしてそこに公開鍵やその他属性を追加してもよいですが、 ここ に各ネットワークにデプロイされているコントラクトがあるのでそれを利用しても問題ないです。(今回は事前にデプロイされているSepoliaネットワークのコントラクトを使用します) ethr-did-resolver 与えられたdidメソッド( did:ethr:development:0xf3beac30c498d9e26865f34fcaa57dbb935b0d74 )をもとにDIDドキュメントを解決したり、VCを検証したりし、DIDドキュメントを返します。 ethr-did DIDの作成や更新をするために使用します。 実装するユースケース 大学の証明書を発行しそれを検証するようなものを想定します。 今回はKYC認証は行わないのでVCを提出しているユーザーがVCの本人かどうかは保証しません。あくまでVCが改ざんされていないかを検証するところのみになります。 実装手順 DIDの登録 大学が検証用の公開鍵をEthereum に登録します。 VCの発行 大学が証明書を発行 VCを検証 準備 Metamask Ethereumでトランザクションを発行するための秘密鍵と検証用公開鍵を取得する https://metamask.io/ja/ Alchemy Etheruemネットワークにアクセスするためにノードを提供してくれるサービス API Keyを取得する https://www.alchemy.com/ Sepoliaテストネットトークン ガス代の支払に使用するためのトークン https://cloud.google.com/application/web3/faucet/ethereum/sepolia https://www.infura.io/faucet/sepolia 実装 今回実装するソースコードは こちら で公開しています。 フロントエンド:React API:Nest.js どちらも簡単なコードで作りこみはしていません。 フロントエンドはフォームとレスポンスをわかりやすく作っているだけなので今回はAPIの app.service.ts を見ていきます。( app.controller.ts はルーティングしかしてませんす) 事前に紹介した三つの機能についてそれぞれ実装を紹介します。 DID登録 import { Injectable } from '@nestjs/common'; import { Resolver } from 'did-resolver'; import { getResolver } from 'ethr-did-resolver'; import { VerifiedCredential, verifyCredential } from 'did-jwt-vc'; import { EthrDID, KeyPair } from 'ethr-did'; import { ethers, Wallet } from 'ethers'; async registerDID(): Promise<void> { const alchemyApiKey = process.env.ALCHEMY_API_KEY!!; const privateKey = process.env.PRIVATE_KEY!!; const wallet = new Wallet(privateKey); const pubkey = wallet.signingKey.publicKey; const address = wallet.address; const provider = new ethers.AlchemyProvider('sepolia', alchemyApiKey); const txSigner = new Wallet(privateKey, provider); const keypair: KeyPair = { privateKey, publicKey: pubkey, address, identifier: pubkey, }; // ユーザーのDID作成 const ethrDid = new EthrDID({ ...keypair, provider: provider, txSigner: txSigner, // こいつを渡さないと失敗する <https://github.com/uport-project/ethr-did/issues/81#issuecomment-1030181286> chainNameOrId: 'sepolia', registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', }); // DID登録 await ethrDid.setAttribute( 'did/pub/Secp256k1/sigAuth/hex', pubkey, 31104000, ); } ここでは検証用に使用する公開鍵をEtheruemに登録する処理を行っています。 登録するスマートコントラクトはすでにライブラリ開発者がでプロしてくれているsepoliaのスマートコントラクトをレジストリとして使用させてもらいます。 0x03d5003bf0e79C5F5223588F347ebA39AfbC3818 Ethereumに登録するトランザクションはこれだけなので公開鍵の登録が終わったらガス代がこれ以降かかることはありません。 DID作成の時には公開鍵、秘密鍵、EOAアドレス、AlchemyProvider、Wallet、デプロイ先のチェーン、スマートコントラクトのアドレスを渡します。 const keypair: KeyPair = { privateKey, publicKey: pubkey, address, identifier: pubkey, }; // ユーザーのDID作成 const ethrDid = new EthrDID({ ...keypair, provider: provider, txSigner: txSigner, // こいつを渡さないと失敗する <https://github.com/uport-project/ethr-did/issues/81#issuecomment-1030181286> chainNameOrId: 'sepolia', registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', }); そして公開鍵の登録の際には公開鍵の形式と用途を指定して登録します。 登録方法は このように なっており、今回は以下のような意味を持っています・ DIDの公開鍵 であること。 Secp256k1 曲線に基づいた公開鍵暗号アルゴリズムを使用していること。 署名と認証 のために使用される公開鍵であること。 公開鍵の値は 16進数形式 で表現されていること。 // DID登録 await ethrDid.setAttribute( 'did/pub/Secp256k1/sigAuth/hex', pubkey, 31104000, ); VCの発行 async issueVc( hodlerAddress: string, type: string, name: string, ): Promise<string> { const vcpayload = { '@context': ['<https://www.w3.org/2018/credentials/v1>'], type: ['VerifiableCredential'], issuer: 'did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8', issuanceDate: new Date().toISOString(), credentialSubject: { id: `did:ethr:sepolia:${hodlerAddress}`, degree: { type: type, name: name, }, }, proof: { type: 'EcdsaSecp256k1Signature2019', created: new Date().toISOString(), proofPurpose: 'assertionMethod', verificationMethod: 'did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1', }, }; const alchemyApiKey = process.env.ALCHEMY_API_KEY!!; const privateKey = process.env.PRIVATE_KEY!!; const wallet = new Wallet(privateKey); const pubkey = wallet.signingKey.publicKey; const address = wallet.address; const provider = new ethers.AlchemyProvider('sepolia', alchemyApiKey); const txSigner = new Wallet(privateKey, provider); const keypair: KeyPair = { privateKey, publicKey: pubkey, address, identifier: address, }; // ユーザーのDID作成 const ethrDid = new EthrDID({ ...keypair, provider: provider, txSigner: txSigner, // こいつを渡さないと失敗する <https://github.com/uport-project/ethr-did/issues/81#issuecomment-1030181286> chainNameOrId: 'sepolia', registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', }); const vc = await ethrDid.signJWT(vcpayload); return vc; } VCにもW3Cが定めている規格が存在しています。 https://www.w3.org/TR/vc-data-model-2.0/ 今回は実装に焦点を当てるため詳細な説明は行いませんが、発行したいVCに合うような形でVCを作成する必要があります。 以下は今回使用するVCの簡単な紹介です。 @context VCが準拠する 標準的なデータ構造の文脈 を示します。 ['<https://www.w3.org/2018/credentials/v1>'] はW3Cが定めたVerifiable Credentialsの標準的な構造に従うことを示します。 type VCの種類を示すプロパティです。 ['VerifiableCredential'] は、この証明書が検証可能な証明書であることを明示しています。 issuer 証明書の発行者(Issuer)のDIDを示します。 この場合、発行者は EthereumのSepoliaテストネット 上にある did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8 というDIDを持つエンティティです。 issuanceDate この証明書が発行された 日付 を示します。 new Date().toISOString() を使って現在の日時をISO 8601形式で自動的に生成します。 credentialSubject 証明書が関連する 主体(Credential Subject)を定義します。このVCに記載される情報の対象 です。 id: did:ethr:sepolia:${hodlerAddress} では、証明書の対象となる人物や組織のDIDを示します。この部分は hodlerAddress という変数で動的に設定されるEthereumのアドレスです。 degree フィールドは証明書の内容を示しています。具体的には「学位」情報として、 type (学位の種類)と name (学位の名称)が含まれています。これらも変数 type と name によって動的に設定される情報です。 proof このVCがどのように 署名され、検証されるか を示す情報です。 type : EcdsaSecp256k1Signature2019 は、 Secp256k1 楕円曲線を使ったECDSA(楕円曲線デジタル署名アルゴリズム)による署名方式を指定しています。これはEthereumなどのブロックチェーンで広く使われている暗号方式です。 created : 署名が生成された日時です。ここでも new Date().toISOString() が使われ、現在の日時が設定されます。 proofPurpose : この署名が何のために行われるのかを示します。 assertionMethod は、発行者が証明書の内容を**主張(assert)**するために行った署名であることを意味します。 verificationMethod : 証明書の署名を 検証するための公開鍵 や検証方法を指定しています。この場合、 did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1 が指定されています。この公開鍵は #delegate-1 という識別子で特定されています。 またVCはJWTトークンとして返却されます。 const vc = await ethrDid.signJWT(vcpayload); 例えば以下のような内容でVCを作成しようとすると 以下のようなトークンが返ってきます。 これがVCになります。 eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NkstUiJ9.eyJpYXQiOjE3Mjk1OTE2ODcsIkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sInR5cGUiOlsiVmVyaWZpYWJsZUNyZWRlbnRpYWwiXSwiaXNzdWVyIjoiZGlkOmV0aHI6c2Vwb2xpYToweDE0NTI0MjI4NkFFODE4NGNBODg1RTZCMTM0RTFBMWJBNzM4NThCRTgiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTEwLTIyVDEwOjA4OjA3LjUwNFoiLCJjcmVkZW50aWFsU3ViamVjdCI6eyJpZCI6ImRpZDpldGhyOnNlcG9saWE6MHhFNDk1RkEwMmI3N0I3ZmVFMzVBMDQ2ODYwNDhEMDg0NzY1MTczMThFIiwiZGVncmVlIjp7InR5cGUiOiLmg4XloLHlt6XlraYiLCJuYW1lIjoi5bel5a2mIn19LCJwcm9vZiI6eyJ0eXBlIjoiRWNkc2FTZWNwMjU2azFTaWduYXR1cmUyMDE5IiwiY3JlYXRlZCI6IjIwMjQtMTAtMjJUMTA6MDg6MDcuNTA0WiIsInByb29mUHVycG9zZSI6ImFzc2VydGlvbk1ldGhvZCIsInZlcmlmaWNhdGlvbk1ldGhvZCI6ImRpZDpldGhyOnNlcG9saWE6MHgxNDUyNDIyODZBRTgxODRjQTg4NUU2QjEzNEUxQTFiQTczODU4QkU4I2RlbGVnYXRlLTEifSwiaXNzIjoiZGlkOmV0aHI6c2Vwb2xpYToweDE0NTI0MjI4NkFFODE4NGNBODg1RTZCMTM0RTFBMWJBNzM4NThCRTgifQ.nMBiW1Yi5vyghoorBRAwMIFHTRfATn_P3hv0ZF_aeeUowti7epUep90ldsuubkylccAmjlIiZwGcB9SgCd5IWAA VCの検証 最後に先ほど取得したVCを検証してみます。 async verifyVc(vc: string): Promise<VerifiedCredential | boolean> { const providerConfig = { // While experimenting, you can set a rpc endpoint to be used by the web3 provider rpcUrl: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_API_KEY}`, // You can also set the address for your own ethr-did-registry (ERC1056) contract registry: '0x03d5003bf0e79C5F5223588F347ebA39AfbC3818', name: 'sepolia', // this becomes did:ethr:development:0x... }; // It's recommended to use the multi-network configuration when using this in production // since that allows you to resolve on multiple public and private networks at the same time. // getResolver will return an object with a key/value pair of { "ethr": resolver } where resolver is a function used by the generic did resolver. const ethrDidResolver = getResolver(providerConfig); const didResolver = new Resolver(ethrDidResolver); try { const result = await verifyCredential(vc, didResolver); return result; } catch (error) { return false; } } DIDリゾルバーを使用して発行元のDIDドキュメントを取得し、ドキュメントから公開鍵を取得しユーザーから提供されたVCを検証します。 DIDリゾルバーはDIDメソッドごとに異なります。 DIDメソッドを管理している団体がDIDリゾルバを提供していることが多いようです。今回はERC1056で紹介されていたDIDリゾルバを使用します。 DIDの解決から検証までは以下のコードになります。 const ethrDidResolver = getResolver(providerConfig); const didResolver = new Resolver(ethrDidResolver); const result = await verifyCredential(vc, didResolver); 提供されているライブラリが優秀すぎて何をしているのかよくわからないので順に解説していきます。 まず最初の2行でDIDリゾルバーの設定を行います。 これはネットワークやリゾルバが参照するコントラクトのアドレスを設定します。 最後の1行でDIDを解決するところからVCの検証まで一括して行っています。 DIDの解決では先ほどのJWTトークンの中にあるissuerというプロパティのDIDを解決します。 参考までに先ほどのJWTトークンをデコードしてみます。 { "iat": 1729591687, "@context": [ "<https://www.w3.org/2018/credentials/v1>" ], "type": [ "VerifiableCredential" ], "issuer": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8", "issuanceDate": "2024-10-22T10:08:07.504Z", "credentialSubject": { "id": "did:ethr:sepolia:0xE495FA02b77B7feE35A04686048D08476517318E", "degree": { "type": "情報工学", "name": "工学" } }, "proof": { "type": "EcdsaSecp256k1Signature2019", "created": "2024-10-22T10:08:07.504Z", "proofPurpose": "assertionMethod", "verificationMethod": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1" }, "iss": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8" } これを見ると did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8 このDIDを解決していることがわかります。 続いてこのDIDを解決してみましょう。 DID解決を行ってくれる Universal Resolver というサイトがあります。 ここで先ほどのDIDを入力すると以下のような結果になります。 これを見ると以下の公開鍵で検証すればよいことがわかります。 今回検証に使うのは proof.verificationMethod に指定されているため 04a48ac40eade831fa352e56aaaab5396cf7f87627913543cf6d1dff5f5c0b496da62401ea4b0629fb3370d56855b2cf32d333e5c1bd0e53cf53722187d7eb3ff1 こちらの公開鍵を使用すればよいことがわかります。 "proof": { "type": "EcdsaSecp256k1Signature2019", "created": "2024-10-22T10:08:07.504Z", "proofPurpose": "assertionMethod", "verificationMethod": "did:ethr:sepolia:0x145242286AE8184cA885E6B134E1A1bA73858BE8#delegate-1" }, 実際に今回のサンプルで検証してみるとvefified:trueと帰ってくるので検証に成功していることがわかります。 最後の検証の部分で渡しているものはVCのみで発行者の情報などはVCからすべて取得できているため発行者に検証に関する問い合わせは行っていないことがわかります。 終わりに 今回はEthereumを使用したDID/VCを実装してみました。 DID/VCを手軽に実装している記事が少ないため実装にはかなり苦労しましたが、動くものを見ると具体的なイメージがわきやすいですね。 DID/VCについてはまだまだ不勉強な部分も多く、デファクトスタンダードと呼ばれるものもないため今後の動向に注目しつつ、今回説明できなかったDID/VCの細かい部分も今後紹介できたらと思います。 ご覧いただきありがとうございます! この投稿はお役に立ちましたか? 役に立った 役に立たなかった 0人がこの投稿は役に立ったと言っています。 The post EthereumでDID/VCを実装してみる first appeared on SIOS Tech. Lab .