こんにちは。GMOグローバルサイン・ホールディングスCTO室で分散型IDの研究をしている開発者の神沼@t_kanumaです。
以下の拙著に記載した通り、 Ariesは最新のAIP2.0からはDIF持ちの仕様群をインポートしており、相互運用性を高めています。
Hyperledger Ariesを中心にSSIの技術的な動向について(2022年後半~2023年前半)
AIP2.0ではJSON-LD Credentialを扱えるのが大きな特徴ですが、この記事ではそこに行く前に、ACA-Py/AFJを使いAIP2.0上でのIndy AnonCredsの発行と検証を試してみます。
仕様セット
ToIP Technical Stackのレイヤーごとに、今回使っている仕様について、これまでの拙著群で使っていた仕様セットと比較します。
ToIP Tech Stack | これまで (AIP1.0主体) | 今回 (Layer3はAIP2.0主体) |
---|---|---|
Layer 1 | did:sov | 左記と同様 |
Layer 2 | DIDComm v1, Connection Protocol, OOB Protocol, Aries独自のdid:peer相当のPairwise DID | 左記と同様 |
Layer 3 | Indy AnonCreds, Issue Credential Protocol 1.0, Present Proof Protocol 1.0 | Indy AnonCreds, Issue Credential Proof 2.0, Present Proof Protocol 2.0 |
以下、ポイントです。
- Layer 3の太文字にした部分が今回の試行における重要な仕様です。
- Layer 2の理想は、メッセージ交換にDIDComm v2、Pairwise DIDにdid:peer、コネクション生成にDID Exchange Protocol in Out-of-Band Protocolを使うのが理想と思いますが、ACA-PyがAIP2.0のフル実装に向けた途中であるためまだ叶いません。AIP1.0のままになっています。
Issue Credential/Present Proof Protocolにおけるv1とv2の違い
AnonCredsに限定すると、大きな違いは無いように思えます。
1つ違いとしては、Proof Present Protocolにてv1ではVerifierによる検証結果をHolderに伝えることはプロトコルの対象外であったのに対し、v2ではそこも対象になっています。
(詳細はv1、v2双方の上記RFC内の状態遷移図をご参照ください。)
コネクションレスなProof提示と検証
コネクションを生成するとは、Agent間にてDID Documentの交換から互いにDIDCommメッセージを送れる状況にすることだと考えます。コネクションを作った上で、Agent同士がVC発行とProof検証の双方向のやり取りを行うわけです。(ご参考までにコネクションとDIDCommについて以下の拙著をご紹介します。)
- 【Hyperledger Aries】DIDCommとJSON Object Signing and Encryption
- 【Hyperledger Aries】ACA-Pyと3つのコネクション生成プロトコル
Aries RFC 0302: Aries Interop Profileによれば、AIP2.0からHolder-Verifier間でのProof提示検証、つまりはPresent Proof Protocol v2においては、コネクションを事前に作成しなくても、それを執り行うことができる旨が書かれています。
Aries Agents must be able to establish connections, exchange credentials and complete a connection-less proof-request/proof transaction.
その手法について平たく述べると、VerifierがHolderにQRコードやメール内リンクの形式としてOut-Of-BandでHolderに送るInvitation情報(Verifierの公開鍵とDIDCommエンドポイントを持つ)に、Holderに要求するProofの情報も合わせて載せます。コネクション有りの場合は段階的に行うところを一度に済ませるイメージです。
Holderはエンドポイントに向けて、要求されたProofをDIDCommメッセージとして送ります。両者は後腐れなくそこで関係が終了するわけです。
以下のRFCより、コネクションを生成するシーケンス(状態遷移図)を確認すると、双方向で複数回のメッセージ送受信が行われるわけで、ここを省くことができ、時間を短縮できるのがメリットだと考えます。
- Aries RFC 0023: DID Exchange Protocol 1.0 – State Machine Table
- Aries RFC 0160: Connection Protocol – State Machine Table
ここで、コネクション有り無しそれぞれにおいて、有用になるケースを考えます。
コネクションレスを使った方が良いケースについて結論から先に述べると、それはコネクションを再利用しないケースだと考えます。言い換えると、常にOut-Of-Bandでプロトコルを開始するケースです。コネクションレスということはセッションが無く、その場限りである検証と言えると思います。例えば、コンビニやスーパーにおける支払いのように、あるHolderが複数回のProof提示を行うとしてもそれぞれの回においてHolderは同一人物として認識されないケースです。結果としてHolder-Verifier間のUX形式が、例えばQRコード読み取りをトリガーになるなど対面になる場合が多いのではないかと考えます。
コネクション有りのメリットは、コネクションを再利用することができることです。Holderの同一性を認識し、Proof提示を何度も要求するケースで有用だと考えます。例えば、拙著:SSI向きのユースケースを想定したプロトタイプ開発でご紹介したプロトタイプシステムでは、(機能としては未実装ですが)コネクションを再利用し、VerifierはOut-Of-BandではなくDIDCommで特定のHolderに再度のProof提示を要求することができます。
今回は有り無し両方のパターンを試してみます。
なお、IssuerにコネクションレスなVC発行の機能がないのは、プライバシーが理由だと考えます。この機能の存在を仮定すると、IssuerからHolderに送るInvitationの中にVC発行オファーの内容も載ることになります。オファーの内容にはVCのClaimの値を含むわけですが、この情報がQRコードやメール内のリンクなどOut-Of-Bandで、すなわちDIDCommの外を行き交うのは危険だということだと思います。
環境
拙著【Hyperledger Indy/Aries】AFJでReact Native製Holder Agentを作りVCモデルを回す(前編)と同様に、ローカルPCのDocker上に各要素をコンテナとして環境を構築します。
また同様に、Issuer/Verifierについては、私がControllerの代わりにSwagger UIからAdmin APIをコールします。HolderについてはControllerとしてNode.js上で動作するTypeScriptコードを実装します。
OSSバージョン
- von-network: 1.7.2
- ACA-Py: 0.8.1
- AFJ
- @aries-framework/core: 0.3.3
- @aries-framework/node: 0.3.3
実装
各Agentが属するNetworkを作成しておきます。
docker network create -d bridge --subnet=172.19.0.0/16 agent_nw
aries-cloudagent-python/docker配下にIssuer、Mediator、Verifier各々のディレクトリを作り、その中にdocker-compose.ymlと.envを作成します。
Issuer
version: '1.0'
services:
acapy:
build:
context: ../..
dockerfile: ./docker/Dockerfile.run
ports:
- "${DIDCOMM_PORT}:${DIDCOMM_PORT}"
- "${ADMIN_API_PORT}:${ADMIN_API_PORT}"
volumes:
- type: bind
source: /Users/takahiro-kanuma/VSCodeProjects/sandbox/ssi-sandbox/oss/aca-py-${ACAPY_VERSION}/docker/issuer
target: /home/indy/logs
entrypoint: /bin/bash
command: [
"-c",
"aca-py \
start \
--label ${LABEL} \
--inbound-transport http '0.0.0.0' ${DIDCOMM_PORT} \
--endpoint http://${IP}:${DIDCOMM_PORT} \
--outbound-transport http \
--admin '0.0.0.0' ${ADMIN_API_PORT} \
--admin-insecure-mode \
--auto-ping-connection \
--auto-respond-messages \
--auto-accept-invites \
--auto-accept-requests \
--preserve-exchange-records \
--wallet-type indy \
--wallet-name ${WALLET_NAME} \
--wallet-key ${WALLET_KEY} \
--auto-provision \
--genesis-url ${LEDGER_URL}/genesis \
--seed ${WALLET_SEED} \
--log-file /home/indy/logs/acapy.log \
--log-level ${LOG_LEVEL} "
]
networks:
agent_agent:
ipv4_address: ${IP}
von_von:
networks:
von_von:
external: true
agent_nw:
external: true
# .env
COMPOSE_PROJECT_NAME=issuer
LABEL=issuer
IP=172.19.0.3
DIDCOMM_PORT=8030
ADMIN_API_PORT=8031
WALLET_NAME=issuer-wallet
WALLET_KEY=thisissecret
LEDGER_URL=http://172.20.0.1:9000
LOG_LEVEL=info
WALLET_SEED=gmogshd0000000000000000000000000
ACAPY_VERSION=0.8.1
Verifier
version: '1.0'
services:
acapy:
build:
context: ../..
dockerfile: ./docker/Dockerfile.run
ports:
- "${DIDCOMM_PORT}:${DIDCOMM_PORT}"
- "${ADMIN_API_PORT}:${ADMIN_API_PORT}"
volumes:
- type: bind
source: /Users/takahiro-kanuma/VSCodeProjects/sandbox/ssi-sandbox/oss/aca-py-${ACAPY_VERSION}/docker/verifier
target: /home/indy/logs
entrypoint: /bin/bash
command: [
"-c",
"aca-py \
start \
--label ${LABEL} \
--inbound-transport http '0.0.0.0' ${DIDCOMM_PORT} \
--endpoint http://${IP}:${DIDCOMM_PORT} \
--outbound-transport http \
--admin '0.0.0.0' ${ADMIN_API_PORT} \
--admin-insecure-mode \
--auto-ping-connection \
--auto-respond-messages \
--auto-accept-invites \
--auto-accept-requests \
--preserve-exchange-records \
--wallet-type indy \
--wallet-name ${WALLET_NAME} \
--wallet-key ${WALLET_KEY} \
--auto-provision \
--genesis-url ${LEDGER_URL}/genesis \
--wallet-local-did \
--log-file /home/indy/logs/acapy.log \
--log-level ${LOG_LEVEL} "
]
networks:
agent:
ipv4_address: ${IP}
von_von:
networks:
von_von:
external: true
agent_nw:
external: true
# .env
COMPOSE_PROJECT_NAME=verifier
LABEL=verifier
IP=172.19.0.2
DIDCOMM_PORT=8020
ADMIN_API_PORT=8021
WALLET_NAME=verifier-wallet
WALLET_KEY=thisissecret
LEDGER_URL=http://172.20.0.1:9000
LOG_LEVEL=info
ACAPY_VERSION=0.8.1
Mediator
version: '1.0'
services:
acapy:
build:
context: ../..
dockerfile: ./docker/Dockerfile.run
ports:
- "${DIDCOMM_HTTP_PORT}:${DIDCOMM_HTTP_PORT}"
- "${DIDCOMM_WS_PORT}:${DIDCOMM_WS_PORT}"
- "${ADMIN_API_PORT}:${ADMIN_API_PORT}"
volumes:
- type: bind
source: /Users/takahiro-kanuma/VSCodeProjects/sandbox/ssi-sandbox/oss/aca-py-${ACAPY_VERSION}/docker/mediator
target: /home/indy/logs
entrypoint: /bin/bash
command: [
"-c",
"aca-py \
start \
--label ${LABEL} \
--inbound-transport http '0.0.0.0' ${DIDCOMM_HTTP_PORT} \
--inbound-transport ws '0.0.0.0' ${DIDCOMM_WS_PORT} \
--endpoint http://${IP}:${DIDCOMM_HTTP_PORT} ws://${IP}:${DIDCOMM_WS_PORT} \
--outbound-transport http \
--outbound-transport ws \
--admin '0.0.0.0' ${ADMIN_API_PORT} \
--admin-insecure-mode \
--auto-ping-connection \
--auto-respond-messages \
--auto-accept-invites \
--auto-accept-requests \
--preserve-exchange-records \
--wallet-type indy \
--wallet-name ${WALLET_NAME} \
--wallet-key ${WALLET_KEY} \
--wallet-local-did \
--auto-provision \
--genesis-url ${LEDGER_URL}/genesis \
--open-mediation \
--log-file /home/indy/logs/acapy.log \
--log-level ${LOG_LEVEL}"
]
networks:
agent_agent:
ipv4_address: ${IP}
von_von:
networks:
von_von:
external: true
agent_nw:
external: true
# .env
COMPOSE_PROJECT_NAME=mediator
LABEL=mediator
IP=172.19.0.4
DIDCOMM_HTTP_PORT=8040
DIDCOMM_WS_PORT=8042
ADMIN_API_PORT=8041
WALLET_NAME=mediator-wallet
WALLET_KEY=thisissecret
LEDGER_URL=http://172.20.0.1:9000
LOG_LEVEL=info
ACAPY_VERSION=0.8.1
Holder Controller
前述の拙著内のコードと同じであるため、割愛します。
動作確認
Indy LedgerへのIssuerのPublic DIDの登録、及びSchemaとCredential Definitionの登録は割愛します。また、HolderとMediator、及びHolderとIssuer/Verifierのコネクション生成部分も割愛します。
VC発行
以下、Issuer ACA-PyのAdmin APIにおいて、Issue Credential Protocol 2.0でVCを発行するEndpointの呼び出しです。(正確にはプロトコルの開始点となるHolderへのオファー送信です。オプションとしてauto_issue=trueにしているため、HolderからのVC発行リクエスト受付後、Endpointを叩かなくてもAgent(ACA-Py)が自動でVC発行まで執り行います。)
ポイントはリクエストボディのfilterプロパティです。この中にAnonCreds発行の場合はindyプロパティを、JSON-LD Credentials発行の場合はld_proofプロパティを記述します。
curl -X 'POST' \
'http://localhost:8031/issue-credential-2.0/send-offer' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"connection_id": "a3d763cd-0e5c-4e14-80ea-32c34f588c5d",
"credential_preview": {
"@type": "issue-credential/2.0/credential-preview",
"attributes": [
{
"mime-type": "text/plain",
"name": "age",
"value": "40"
}
]
},
"filter": {
"indy": {
"cred_def_id": "Vtw2qgmuMVy3rk2ipw7Vxt:3:CL:8:2.0"
}
},
"trace": true,
"auto_issue": true,
"auto_remove": false,
"comment": "try v2"
}'
以下、Holder側でのログです。VCが発行されたことを確認できました。
Proof検証(コネクション有りバージョン)
以下、Verifier ACA-PyのAdmin APIにおいて、Present Proof Protocol 2.0でProofを提示するEndpointの呼び出しです。(正確にはプロトコルの開始点となるHolderへのProof要求の送信です。オプションとしてauto_verify=trueにしているため、HolderからのProof提示後、Endpointを叩かなくてもAgent(ACA-Py)が自動で検証まで執り行います。)
ポイントはリクエストボディのpresentation_requestプロパティです。この中にAnonCreds発行の場合はindyプロパティを、JSON-LD Credentials発行の場合はdifプロパティを記述します。
curl -X 'POST' \
'http://localhost:8021/present-proof-2.0/send-request' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"connection_id": "159acb80-22ce-4c96-9709-b3686a797970",
"presentation_request": {
"indy": {
"name": "Proof request",
"requested_attributes": {
},
"requested_predicates": {
"0_name_uuid": {
"name": "age",
"p_type": ">=",
"p_value": 30,
"restrictions": [
{
"cred_def_id": "Vtw2qgmuMVy3rk2ipw7Vxt:3:CL:8:2.0"
}
]
}
},
"version": "1.0"
}
},
"auto_verify": true,
"comment": "try v2",
"trace": true
}'
Verifier側でも検証が完了したことを確認できました。Present Proof Protocol v2ではHolder側からもそれを確認できます。(ただし、Holderが知れるのは検証が完了したことで、Proofの妥当性に対する成否までは知ることができません。)
以下、Holder側でのログです。
Proof検証(コネクション無しバージョン)
以下に手順を示します。
1. Verfier側: Proof要求の作成
まずInvitationに載せるProofの要求内容を作成します。
(以下のAdmin API Endpointを呼び出した時点ではHolderに情報は送られません。)
curl -X 'POST' \
'http://localhost:8021/present-proof-2.0/create-request' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"presentation_request": {
"indy": {
"name": "Proof request",
"requested_attributes": {
},
"requested_predicates": {
"0_name_uuid": {
"name": "age",
"p_type": ">=",
"p_value": 30,
"restrictions": [
{
"cred_def_id": "Vtw2qgmuMVy3rk2ipw7Vxt:3:CL:8:2.0"
}
]
}
},
"version": "1.0"
}
},
"auto_verify": true,
"comment": "try v2",
"trace": true
}'
2. Verifier側: Proofリクエストを含むInvitationの作成
上記No.1で要求を作ると、それに対しIDが振られます(Admin API呼び出しのレスポンスとして取得します)。それをInvitation作成のAPI Endpointのリクエストボディに渡します。以下のattachments配列プロパティ内のidプロパティです。
curl -X 'POST' \
'http://localhost:8021/out-of-band/create-invitation' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"accept": [
"didcomm/aip1",
"didcomm/aip2;env=rfc19"
],
"alias": "Verifier",
"attachments": [
{
"id": "602e4828-5f47-4658-8a94-428df713a09a",
"type": "present-proof"
}
],
"my_label": "verifier"
}'
上記のAdmin APIリクエストのレスポンスとして、Invitationが作成されます。
"invitation": {
"@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/out-of-band/1.1/invitation",
"@id": "e14023e2-3ce0-41e2-a7c7-9685e6aa0eb1",
"label": "verifier",
"services": [
{
"id": "#inline",
"type": "did-communication",
"recipientKeys": [
"did:key:z6MkuwtZ3DgYMc2JJshTXL8K6EuBSkQ9YiRX54VbT8zkXxkt"
],
"serviceEndpoint": "http://172.19.0.2:8020"
}
],
"accept": [
"didcomm/aip1",
"didcomm/aip2;env=rfc19"
],
"requests~attach": [
{
"@id": "request-0",
"mime-type": "application/json",
"data": {
"json": {
"@type": "did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/present-proof/2.0/request-presentation",
"@id": "0f6a3db5-d768-4425-88a6-b65a100828bb",
"~trace": {
"target": "log",
"full_thread": true,
"trace_reports": []
},
"comment": "try v2",
"formats": [
{
"attach_id": "indy",
"format": "hlindy/proof-req@v2.0"
}
],
"will_confirm": true,
"request_presentations~attach": [
{
"@id": "indy",
"mime-type": "application/json",
"data": {
"base64": "eyJuYW1lIjogIlByb29mIHJlcXVlc3QiLCAicmVxdWVzdGVkX2F0dHJpYnV0ZXMiOiB7fSwgInJlcXVlc3RlZF9wcmVkaWNhdGVzIjogeyIwX25hbWVfdXVpZCI6IHsibmFtZSI6ICJhZ2UiLCAicF90eXBlIjogIj49IiwgInBfdmFsdWUiOiAzMCwgInJlc3RyaWN0aW9ucyI6IFt7ImNyZWRfZGVmX2lkIjogIlZ0dzJxZ211TVZ5M3JrMmlwdzdWeHQ6MzpDTDo4OjIuMCJ9XX19LCAidmVyc2lvbiI6ICIxLjAiLCAibm9uY2UiOiAiMTE4NjQ0MDA4NDE5NDE2NDEzMTAzNjAwNyJ9"
}
}
],
"~thread": {
"pthid": "e14023e2-3ce0-41e2-a7c7-9685e6aa0eb1"
}
}
}
}
]
}
3. Holder側: Invitationの受け付けとProofの提示
実運用では、このInvitationをQRコードやメール内のリンクとしてHolderに渡すわけですが、今回は直接Holder Controller内のコードに埋め込みます。
前述のHolderのコードの中のVC発行リスナーを修正します。VCを受け取った直後に、エンコードされたURL形式のInvitationを受け付けるようにします。(以下に示すコードの11~16行目)
HolderはInvitationを読み込むと、VerifierにProofを提示します。
case CredentialState.Done: {
// Credentialを受理した後に動く。
logger.info(`a credential has been accepted.`);
const storedCredentialRecord = await holder.credentials.findById(
credentialRecord.id
);
logger.info(
`stored credential: ${JSON.stringify(storedCredentialRecord)}`
);
logger.info("connectionless present proof");
await holder.oob.receiveInvitationFromUrl(
"http://172.19.0.2:8020?oob=eyJAdHlwZSI6ICJkaWQ6c292OkJ6Q2JzTlloTXJqSGlxWkRUVUFTSGc7c3BlYy9vdXQtb2YtYmFuZC8xLjEvaW52aXRhdGlvbiIsICJAaWQiOiAiZTE0MDIzZTItM2NlMC00MWUyLWE3YzctOTY4NWU2YWEwZWIxIiwgImxhYmVsIjogInZlcmlmaWVyIiwgInNlcnZpY2VzIjogW3siaWQiOiAiI2lubGluZSIsICJ0eXBlIjogImRpZC1jb21tdW5pY2F0aW9uIiwgInJlY2lwaWVudEtleXMiOiBbImRpZDprZXk6ejZNa3V3dFozRGdZTWMySkpzaFRYTDhLNkV1QlNrUTlZaVJYNTRWYlQ4emtYeGt0Il0sICJzZXJ2aWNlRW5kcG9pbnQiOiAiaHR0cDovLzE3Mi4xOS4wLjI6ODAyMCJ9XSwgImFjY2VwdCI6IFsiZGlkY29tbS9haXAxIiwgImRpZGNvbW0vYWlwMjtlbnY9cmZjMTkiXSwgInJlcXVlc3RzfmF0dGFjaCI6IFt7IkBpZCI6ICJyZXF1ZXN0LTAiLCAibWltZS10eXBlIjogImFwcGxpY2F0aW9uL2pzb24iLCAiZGF0YSI6IHsianNvbiI6IHsiQHR5cGUiOiAiZGlkOnNvdjpCekNic05ZaE1yakhpcVpEVFVBU0hnO3NwZWMvcHJlc2VudC1wcm9vZi8yLjAvcmVxdWVzdC1wcmVzZW50YXRpb24iLCAiQGlkIjogIjBmNmEzZGI1LWQ3NjgtNDQyNS04OGE2LWI2NWExMDA4MjhiYiIsICJ-dHJhY2UiOiB7InRhcmdldCI6ICJsb2ciLCAiZnVsbF90aHJlYWQiOiB0cnVlLCAidHJhY2VfcmVwb3J0cyI6IFtdfSwgImNvbW1lbnQiOiAidHJ5IHYyIiwgImZvcm1hdHMiOiBbeyJhdHRhY2hfaWQiOiAiaW5keSIsICJmb3JtYXQiOiAiaGxpbmR5L3Byb29mLXJlcUB2Mi4wIn1dLCAid2lsbF9jb25maXJtIjogdHJ1ZSwgInJlcXVlc3RfcHJlc2VudGF0aW9uc35hdHRhY2giOiBbeyJAaWQiOiAiaW5keSIsICJtaW1lLXR5cGUiOiAiYXBwbGljYXRpb24vanNvbiIsICJkYXRhIjogeyJiYXNlNjQiOiAiZXlKdVlXMWxJam9nSWxCeWIyOW1JSEpsY1hWbGMzUWlMQ0FpY21WeGRXVnpkR1ZrWDJGMGRISnBZblYwWlhNaU9pQjdmU3dnSW5KbGNYVmxjM1JsWkY5d2NtVmthV05oZEdWeklqb2dleUl3WDI1aGJXVmZkWFZwWkNJNklIc2libUZ0WlNJNklDSmhaMlVpTENBaWNGOTBlWEJsSWpvZ0lqNDlJaXdnSW5CZmRtRnNkV1VpT2lBek1Dd2dJbkpsYzNSeWFXTjBhVzl1Y3lJNklGdDdJbU55WldSZlpHVm1YMmxrSWpvZ0lsWjBkekp4WjIxMVRWWjVNM0pyTW1sd2R6ZFdlSFE2TXpwRFREbzRPakl1TUNKOVhYMTlMQ0FpZG1WeWMybHZiaUk2SUNJeExqQWlMQ0FpYm05dVkyVWlPaUFpTVRFNE5qUTBNREE0TkRFNU5ERTJOREV6TVRBek5qQXdOeUo5In19XSwgIn50aHJlYWQiOiB7InB0aGlkIjogImUxNDAyM2UyLTNjZTAtNDFlMi1hN2M3LTk2ODVlNmFhMGViMSJ9fX19XX0="
);
break;
}
コネクション有りの時と同様、Verifierにて検証が完了し、その結果がHolderに送信されたことを確認できました。(ただし、Holderが知れるのは検証が完了したことで、Proofの妥当性に対する成否までは知ることができません。)
以下、Holder側のログです。
おわりに
(Layer2が未完全なものの)AIP2.0上でのIndy AnonCredsの発行とコネクションレスなProof検証を遂行できました。次回は同様の環境でJSON-LD Credentialsの発行と検証を試した記事を上げたいと思います。
以上でこの記事を終わります。
どなたかのお役に立てたならば幸いです。