ION DID を認証

DID の認証について、Microsoft の文書「Decentralized Identity」では以下のように言及されている。

To authenticate a DID with an external party, the DID owner discloses a DID to the party. The external party looks up the DID via the DIF Universal Resolver, and the resolver returns the matching DPKI metadata. The external party generates a challenge by using the public key references in the DPKI metadata and performs a handshake with the user. If the user is able to complete the challenge-response handshake, it’s been proven that the user is the owner of DID in question.

DIDを外部と認証するには、DIDの所有者がその相手にDIDを開示します。外部の当事者はDIFユニバーサルリゾルバを介してDIDを検索し、リゾルバは一致するDPKIメタデータを返します。外部当事者はDPKIメタデータ内の公開鍵参照を用いてチャレンジを生成し、ユーザーとハンドシェイクを行います。チャレンジ・レスポンス・ハンドシェイクを完了することができれば、そのユーザーが当該DIDの所有者であることが証明されます。

やってみよう

前作った DID の秘密鍵忘れたのであらためて発行する。

参考: testnet ION で DID を発行

require 'sidetree' require 'bitcoin' require 'net/http' require 'json' include Bitcoin::Opcodes Bitcoin.chain_params = :testnet HOST="localhost" PORT=18332 RPCUSER="user" RPCPASSWORD="password" def bitcoinRPC(method, params) http = Net::HTTP.new(HOST, PORT) request = Net::HTTP::Post.new('/') request.basic_auth(RPCUSER, RPCPASSWORD) request.content_type = 'application/json' request.body = { method: method, params: params, id: 'jsonrpc' }.to_json JSON.parse(http.request(request).body)["result"] end recovery_key = Sidetree::Key.generate update_key = Sidetree::Key.generate signing_key = Sidetree::Key.generate(id: 'signing-key') signing_key.private_key # => <signing-key-priv-key> signing_key.public_key # => #<ECDSA::Point: secp256k1, 0xa8dc69e54de871489221fd3f0a48601e4c3d611e628415e3cccffb3cd4b5bfeb, 0x91193abdbb4d122d8e81fa456be2447a1ac255c2f14a36bcb7eb6a511a8db377> # ちなみに private_key から public_key を導出してもOK # ECDSA::Group::Secp256k1.generator.multiply_by_scalar(signing_key.private_key) # => #<ECDSA::Point: secp256k1, 0xa8dc69e54de871489221fd3f0a48601e4c3d611e628415e3cccffb3cd4b5bfeb, 0x91193abdbb4d122d8e81fa456be2447a1ac255c2f14a36bcb7eb6a511a8db377> service = Sidetree::Model::Service.new("linkeddomains", "LinkedDomains", "https://github.com/shmn7iii") document = Sidetree::Model::Document.new(public_keys: [signing_key], services: [service]) did = Sidetree::DID.create(document, update_key, recovery_key, method: Sidetree::Params::METHODS[:ion]) did.short_form # => did:ion:EiDurCoSY5WW9l31cO0-l4TcozZCDWn4hzHdbo8AU2yEbg ipfs = Sidetree::CAS::IPFS.new create_op = did.create_op chunk_file = Sidetree::Model::ChunkFile.create_from_ops(create_ops: [create_op]) chunk_file_uri = ipfs.write(chunk_file.to_compress) # => "QmWnVDg9pddBw2MGobPAQVRdcKWvvJCCnUnrzVnCquXZAj" 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) # => "QmfDUG6BygdN1phF44vng84XvbwKPmUHq5ifFiqTxChF8t" 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) # => "QmfFhiddLAr7r5FC4xuYZZ39yr68aBspkzHeDPFMPQ7ZRD" anchor_str = Sidetree::Util::AnchoredDataSerializer.serialize(1, core_index_file_uri) # => "1.QmfFhiddLAr7r5FC4xuYZZ39yr68aBspkzHeDPFMPQ7ZRD" address = "mt2jyxWfbAf4KY13QNW6ibXCyDN3TXx4hY" address_private_key = <address-private-key> txid = "508505bc6ee912f24edea2bffa259d7978290ad2aee8ddb23e505ebd7b449645" vout = 1 input_tx = Bitcoin::Tx.parse_from_payload(bitcoinRPC('getrawtransaction',[txid]).htb) input_satoshi = input_tx.outputs[vout].value script_pubkey = input_tx.outputs[vout].script_pubkey outpoint = Bitcoin::OutPoint.from_txid(txid, vout) redeem_script = Bitcoin::Script.new << OP_RETURN << "ion:#{anchor_str}".bth FEE = 0.00003 fee_satoshi = (FEE * (10**8)).to_i change_satoshi = input_satoshi - fee_satoshi tx = Bitcoin::Tx.new tx.in << Bitcoin::TxIn.new(out_point: outpoint) tx.out << Bitcoin::TxOut.new(value: 0, script_pubkey: redeem_script) tx.out << Bitcoin::TxOut.new(value: change_satoshi, script_pubkey: Bitcoin::Script.parse_from_addr(address)) sig_hash = tx.sighash_for_input(0, script_pubkey) key = Bitcoin::Key.from_wif(address_private_key) signature = key.sign(sig_hash) + [Bitcoin::SIGHASH_TYPE[:all]].pack('C') tx.in[0].script_sig << signature tx.in[0].script_sig << key.pubkey.htb tx.verify_input_sig(0, script_pubkey) txid = bitcoinRPC('sendrawtransaction',[tx.to_hex]) # => "03f3f12d47316e2ef28e8082857ae25a83c600371098e115b09504e81fda98d0"

作成した DID は

did:ion:EiDurCoSY5WW9l31cO0-l4TcozZCDWn4hzHdbo8AU2yEbg

認証

ということでデジタル署名の検証をしてみる。

サーバー - クライアント間でのやり取り時はバイナリだと分かりにくいので bth で Hex化する。

# public_key の取得方法は時と場合による気もするけど Resolver から取得する場合は以下 http = Net::HTTP.new('localhost', 3000) request = Net::HTTP::Get.new('/identifiers/did:ion:test:EiDurCoSY5WW9l31cO0-l4TcozZCDWn4hzHdbo8AU2yEbg') response = JSON.parse(http.request(request).body) public_key_jwk = response['didDocument']['verificationMethod'][0]['publicKeyJwk'] x = Base64.urlsafe_decode64(public_key_jwk['x']) y = Base64.urlsafe_decode64(public_key_jwk['y']) point = ECDSA::Format::PointOctetString.decode(["04"].pack("H*") + x + y, ECDSA::Group::Secp256k1) # public_key は ECDSA::Point型 public_key = point

1.サーバーでメッセージのダイジェストと nonce を生成、送信。

# server public_key = point message = 'hogefuga' message_digest = Digest::SHA256::digest(message) message_digest.bth # => "4cac15dfacf86b494af5f22ea6bdb24e1223bf2ef2d6718313a550ea290cda75" nonce = 1 + SecureRandom.random_number(ECDSA::Group::Secp256k1.order - 1) # => 26920509262110851743645976825312457860527529165072961028999441893074496925803 # send message_digest.bth, nonce

2.クライアントで受け取り Signature DER を返却。

# client public_key = point private_key = <signing-key-private-key> # receive message_digest.bth, nonce message_digest = message_digest.bth.htb signature = ECDSA.sign(ECDSA::Group::Secp256k1, private_key, message_digest, nonce) signature_der = ECDSA::Format::SignatureDerString.encode(sig) signature_der.bth # => "3045022100c7bfd40fc9139fd0ab25178b92821fa003cb0f34bdb42a0b44f9de68b8354bc802205dd5ccd2741000e8fe1378a95e6ac93a5a69d3b76b58084f879321cc9700728b" # send signature_der.bth

3.サーバーで受け取り検証。

# server # receive signature_der.bth signature_der = signature_der.bth.htb signature = ECDSA::Format::SignatureDerString.decode(signature_der) result = ECDSA.valid_signature?(public_key, message_digest, signature) # => true

完了!