前回は、IONのDIDを生成した↓
techmedia-think.hatenablog.com
↑では、DID Documentを生成し、IONのプロトコルに沿って識別子であるDIDを計算したけど、実際にこれだけだとローカルで計算してるだけなので、続いて、このDIDをCAS(Content Addressable Storage)とブロックチェーンにアンカリングしてみる。
CASへのアンカリング
Sidetreeでは、以下のファイルでDID操作のデータを管理している。
- Core Proof File:DIDの
Recover
、Deactivate
操作について、それらが適切な操作であることを証明する暗号学的な証明を含むファイル。具体的には、現在のRecovery Keyとそれに対して有効な署名など(Recover
の場合のみ、次のRecovery Keyのコミットメントを含む)。 - Provisional Index File:DIDの
Update
操作を証明するデータ(DIDのsuffixデータ、その最新のUpdate Keyのコミットメントに対応するUpdate Key)に加えて、Provisional Proof FileのURIと各Chunk FileのURIが含まれるファイル。- Provisional Proof File:
Update
操作について、それが適切な操作であることを証明する暗号学的な証明を含むファイル。具体的には、現在のUpdate Keyとそれに対して有効な署名が含まれるファイル。 - Chunk File:DIDの状態を更新する際の、変更のPatchデータや、次のUpdate Keyのコミットメントなどが含まれる。
- Provisional Proof File:
- Core Index File:
Create
、Recover
、Deactivate
の3つのDID操作の値と、関連するProvisional Index FileのCASのURIが定義されたファイル。Recover
、Deactivate
操作の場合は、追加でCore Proof FileのURIが含まれる。
この内、最後のCore Index FileのURIがブロックチェーンにアンカリングされる。URIからこのファイルを取得すれば、このファイルで行われたDID操作がリストされおり、また各関連ファイルを辿れるようになっている。
新規作成したDIDのアンカリング
今回はDIDのCreate
操作をアンカリングしてみる。この場合、作成するのは、Chunk Fileと作成したChunk FileのURIが含まれるProvisional Index Fileおよび、Provisional Index FileのURIとCreateオペレーションのデータが含まれるCore Index Fileの3つ。※ Update/Recover/DeactivateはしないのでProof系のファイルは作らない。
まず、アンカリング対象のDIDを作成(did:ion:test:EiCFPfLlfe_2D8UxjJtGsaE5zsTisGZiOSU16dgq6vHDOw
)↓*1
require 'sidetree' Sidetree::Params.network = Sidetree::Params::Network::TESTNET recovery_key = Sidetree::Key.generate update_key = Sidetree::Key.generate signing_key = Sidetree::Key.generate(id: 'signing-key') document = Sidetree::Model::Document.new(public_keys: [signing_key]) did = Sidetree::DID.create(document, update_key, recovery_key, method: Sidetree::Params::METHODS[:ion])
Long-FormのDIDは、
Long-Form DID: did:ion:test:EiCFPfLlfe_2D8UxjJtGsaE5zsTisGZiOSU16dgq6vHDOw:eyJkZWx0YSI6eyJwYXRjaGVzIjpbeyJhY3Rpb24iOiJyZXBsYWNlIiwiZG9jdW1lbnQiOnsicHVibGljS2V5cyI6W3siaWQiOiJzaWduaW5nLWtleSIsInB1YmxpY0tleUp3ayI6eyJjcnYiOiJzZWNwMjU2azEiLCJrdHkiOiJFQyIsIngiOiJ0TVFEbnFkLUZIRGJjaGxwYzl6b0JsbVlib29hNk9NRVZyNlZTWERhTHpZIiwieSI6IlVzNEtWYy12OFBRYnJWUTRtdjNvczhoVG1lQUZ3OE5XRFJ6TmY4NHltdXMifSwicHVycG9zZXMiOltdLCJ0eXBlIjoiRWNkc2FTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE5In1dLCJzZXJ2aWNlcyI6W119fV0sInVwZGF0ZUNvbW1pdG1lbnQiOiJFaURCUTU2aXJsQnNrcjdaYXdGTGNvSDEzMVp2U0xiZmEyaksxTU9ueS1mZUlRIn0sInN1ZmZpeERhdGEiOnsiZGVsdGFIYXNoIjoiRWlCeExEX1BVNEVlQlpVNXpKV2wxMGtvTUZmTmtjQTZ4TExvZ0FOWTdfakxKQSIsInJlY292ZXJ5Q29tbWl0bWVudCI6IkVpQ0NhR3l5YU4yOFMydUc4MS1kTjl3c1ZQVTZoUTVYb1AxZFJtQ3pONU9uNUEifX0
↑のDIDを実際にアンカリングしてみる。手順は、
- 新規DIDのCreate Operationデータを生成。
- 1のデータを使ってChunk Fileを生成。今回のケースだとCreate OperationのDeltaデータが格納される。つまり、登録するDID Documentが含まれるデータ。
- Chunk FileをIPFSに書き込み、URIを取得。
- 3のURIを持つProvisional Index Fileを生成。今回のケースだとCreateのみなので、2のURIのみ。Updateが含まれる場合は、Provisional Proof FileのURIやオペレーションデータが含まれる。
- Provisional Index FileをIPFSに書き込み、URIを取得。
- 1のCreate Operationのデータと5のProvisional Index FileのURIからCore Index Fileを生成。今回のケースだとCreateのみなので、5のURIとオペレーションデータのみ。UpdateやRecoverがあれば別途Core Proof FileのURIが含まれる
- Core Index FileのURIをIPFSに書き込み、URIを取得。
↑のDIDについて、生成した2のChunk Fileのデータは↓
{"deltas":[{"patches":[{"action":"replace","document":{"publicKeys":[{"id":"signing-key","publicKeyJwk":{"crv":"secp256k1","kty":"EC","x":"tMQDnqd-FHDbchlpc9zoBlmYbooa6OMEVr6VSXDaLzY","y":"Us4KVc-v8PQbrVQ4mv3os8hTmeAFw8NWDRzNf84ymus"},"purposes":[],"type":"EcdsaSecp256k1VerificationKey2019"}],"services":[]}}],"updateCommitment":"EiDBQ56irlBskr7ZawFLcoH131ZvSLbfa2jK1MOny-feIQ"}]}
Provisional Index Fileのデータは↓
{"chunks":[{"chunkFileUri":"QmSVy6tUexQdmzBPpMEGjBs7ajB5dyGKuqZrhZMgvxqt1Q"}]}
Core Index Fileのデータは↓
{"provisionalIndexFileUri":"Qmc6LnGEqYNZ3YKhGNo3fMn8pUVrhXtTWKZEwhWt3H5vMo","operations":{"create":[{"suffixData":{"deltaHash":"EiBxLD_PU4EeBZU5zJWl10koMFfNkcA6xLLogANY7_jLJA","recoveryCommitment":"EiCCaGyyaN28S2uG81-dN9wsVPU6hQ5XoP1dRmCzN5On5A"}}]}}
いずれもIPFSに格納されるデータは↑をGZIP圧縮したデータになる。
sidetreerbに実装してみた↓(事前にIPFS daemonをローカルで実行しておく)。
require 'sidetree' Sidetree::Params.network = Sidetree::Params::Network::TESTNET # DIDの生成 recovery_key = Sidetree::Key.generate update_key = Sidetree::Key.generate signing_key = Sidetree::Key.generate(id: "signing-key") document = Sidetree::Model::Document.new(public_keys: [signing_key]) did = Sidetree::DID.create( document, update_key, recovery_key, method: Sidetree::Params::METHODS[:ion] ) # DIDのCreate Operationを生成 create_op = did.create_op # Chunk Fileを生成 chunk_file = Sidetree::Model::ChunkFile.create_from_ops(create_ops: [create_op]) # IPFSクライアントを初期化(パラメータ省略するとデフォルトでhttp://localhost:5001/api/v0にアクセス) ipfs = Sidetree::CAS::IPFS.new # Chunk FileをIPFSに書き込み chunk_file_uri = ipfs.write(chunk_file.to_compress) # Provisional Index Fileを作成しIPFSに書き込み provisional_index_file = Sidetree::Model::ProvisionalIndexFile.new(chunks: [Sidetree::Model::Chunk.new(chunk_file_uri)]) provisional_index_file_uri = ipfs.write(provisional_index_file.to_compress) # Core Index Fileを作成しIPFSに書き込み core_index_file = Sidetree::Model::CoreIndexFile.new( create_ops: [create_op], provisional_index_file_uri: provisional_index_file_uri ) core_index_file_uri = ipfs.write(core_index_file.to_compress)
最終的なCore Index FileのURIは、
QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW
Bitcoinへのアンカリング
CASへのアンカリングが出来たら、続いてブロックチェーンにアンカリングする。ここで、ブロックチェーンにアンカリングするデータは、Anchor Stringとして定義されているデータで、↑で作成したCore Index FileのURIと、操作の数で構成される。CASにアンカリングされた関連操作はCore Index Fileで参照可能になっているので、そのURIをブロックチェーンにアンカリングしている。
Anchor Stringは、単純に操作数とCore Index FileのURIを.
で連結したデータ(例:1000.QmWd5PH6vyRH5kMdzZRPBnf952dbR4av3Bd7B2wBqMaAcf
)。
なので、今回は、Anchor Stringにprefix(ion:
)を付けた以下のデータをOP_RETURN
を使ってBitcoinのトランザクションに埋め込めばいい。
ion:1.QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW
実際に発行したトランザクションが45e4459c7f3a36124b8b5df5c5b9314c4187833056f89a0c88ad57997fe1b051。OP_RETURNに↑が記録されているのが分かる。
ちなみに、↑のアンカリングTxの手数料が最低額を下回ってるとBitcoin的にはブロックに格納されるけど、IONとしては有効なCoreファイルとしてエラー処理される。
トランザクションがブロックに格納されると、ionノードを起動していれば新しいブロック内のアンカリングトランザクションを検知し、DIDの処理が行われる↓
Fetched 1 Sidetree transactions from blockchain service in 42 ms. CommandSucceededEvent { connectionId: 'localhost:27017', requestId: 13, commandName: 'find', duration: 28, reply: { cursor: { firstBatch: [Array], id: 0, ns: 'ion-testnet-core.transactions' }, ok: 1 } } Downloading core index file 'QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW', max file size limit 1000000 bytes... Downloading file 'QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW', max size limit 1000000... Successfully kicked off downloading/processing of all new Sidetree transactions. Processing previously unresolvable transactions if any... CommandSucceededEvent { connectionId: 'localhost:27017', requestId: 14, commandName: 'find', duration: 1, reply: { cursor: { firstBatch: [], id: 0, ns: 'ion-testnet-core.unresolvable-transactions' }, ok: 1 } } Fetched 0 unresolvable transactions to retry in 1 ms. Event emitted: sidetree_observer_loop_success Waiting for 60 seconds before fetching and processing transactions again. Read and pinned 240 bytes for CID: QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW. Event emitted: sidetree_download_manager_download: {"code":"success"} File 'QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW' of size 240 downloaded. Downloading provisional index file 'Qmc6LnGEqYNZ3YKhGNo3fMn8pUVrhXtTWKZEwhWt3H5vMo', max file size limit 1000000... Downloading file 'Qmc6LnGEqYNZ3YKhGNo3fMn8pUVrhXtTWKZEwhWt3H5vMo', max size limit 1000000... Read and pinned 93 bytes for CID: Qmc6LnGEqYNZ3YKhGNo3fMn8pUVrhXtTWKZEwhWt3H5vMo. Event emitted: sidetree_download_manager_download: {"code":"success"} File 'Qmc6LnGEqYNZ3YKhGNo3fMn8pUVrhXtTWKZEwhWt3H5vMo' of size 93 downloaded. Downloading chunk file 'QmSVy6tUexQdmzBPpMEGjBs7ajB5dyGKuqZrhZMgvxqt1Q', max size limit 10000000... Downloading file 'QmSVy6tUexQdmzBPpMEGjBs7ajB5dyGKuqZrhZMgvxqt1Q', max size limit 10000000... Read and pinned 308 bytes for CID: QmSVy6tUexQdmzBPpMEGjBs7ajB5dyGKuqZrhZMgvxqt1Q. Event emitted: sidetree_download_manager_download: {"code":"success"} File 'QmSVy6tUexQdmzBPpMEGjBs7ajB5dyGKuqZrhZMgvxqt1Q' of size 308 downloaded. Parsed chunk file in 1 ms. CommandSucceededEvent { connectionId: 'localhost:27017', requestId: 23, commandName: 'update', duration: 1, reply: { n: 1, upserted: [ [Object] ], nModified: 0, ok: 1 } } Processed 1 operations. Retry needed: false Transaction 1.QmQPk9Vi9iSm4aAPEd73txGjqwbHhLYWfoUEpG5eYmy2PW is confirmed at 2315260
そして、ローカルで実行中のIONにDIDを問い合わせると↓、DIDの解決に無事成功!
http://localhost:3000/identifiers/did:ion:test:EiCFPfLlfe_2D8UxjJtGsaE5zsTisGZiOSU16dgq6vHDOw
今回の最小限のCreate操作で、CASやブロックチェーンにどうデータをアンカリングしているのかが大体分かってきた。
*1:前回作成したDIDは、DID Document内の鍵のtypeの設定が漏れてたので再生成。