はじめに

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

今回は、Sign-In with Ethereumの上に、ユーザーがどのサービスにどんな操作を許可するかを安全に指定できる仕組みを提案しているERC5573についてまとめていきます!

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

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

概要

ERC4361(Sign-In with Ethereum: SIWE)は、Ethereumアカウントを使ってブロックチェーンの外部にあるサービス(オフチェーンサービス)へ安全にログインする仕組みを定義した規格です。

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

ERC5573で提案されている「ReCaps」は、このSIWEの上に新たな層を設けた仕組みです。 ReCapsは、Ethereumアカウントの所有者が「どのサービスに」、「どの範囲の権限を」、「どのような目的で」委ねるかを明確に理解したうえで同意(informed consent)を与えることを可能にします。 これにより、利用者は自分の権限がどのように使われるのかをコントロールできるようになります。

ここで登場する「Relying Party(信頼する側)」とは、ユーザーが認証を行う対象のサービスやアプリケーションを指します。 ReCapsは、そのRelying Partyに対して特定の範囲(スコープ)で機能を使う許可を与える仕組みです。

なお、ERC5573では、Relying Partyが実際にどのように外部リソースへアクセスするか(つまり認証後の通信方法)は定義の対象外です。 これは各サービスの実装に委ねられます。

動機

SIWE ReCapsは、既存のSIWEの機能を拡張し、開発者がプロトコルやAPIをより簡単に統合できるようにするために考案されました。 この仕組みによって、ユーザーの操作回数を減らし(ユーザーフリクションの削減)、オンチェーン上での状態(トランザクションなどの処理)を最小化しつつ、セキュリティを高めることができます。 特に「ユーザーの同意に基づいた明確な権限付与」と「予測可能で一貫性のある権限管理(deterministic capability objects)」が実現されます。

従来のSIWEでは、Ethereumアカウントを利用して「あるサービス(Relying Party)」に対して認証することは可能でしたが、そのRelying Partyがさらに別の第三者サービス(例えばデータストレージやメッセージングAPIなど)と連携する時、Ethereumアカウントの代理としてアクセス権を与える標準的な方法は存在しませんでした。

この問題を解決するのがReCapsです。 ReCapsは、認証と認可(アクセス許可)の両方を安全に統合する仕組みを提供し、ユーザー体験を損なうことなく、信頼性と安全性を保ちます。 具体的には、あるRelying PartyがEthereumアカウントの代わりに別のサービスへ安全にアクセスできるようにする手段を定義します。

この仕組みは、Web認証の分野で広く使われている「OpenID Connect」と「OAuth2」を組み合わせた考え方とよく似ています。

  • OpenID Connectは、ユーザーの本人確認(認証)を行う仕組みです。
  • OAuth2は、あるアプリケーションに対して特定の権限を与えるための仕組みです。

SIWEがOpenID Connectのように「誰がログインしたか」を確認する役割を担い、ReCapsはOAuth2のように「どの範囲までアクセスを許可するか」を扱う役割を持ちます。 このように、SIWE ReCapsはEthereumのサインインの仕組みを拡張し、認証(authentication)と認可(authorization)の両方を扱う統一的なフレームワークを提供します。

仕様

ERC5573は、以下の三者を想定して設計されています。

対象 目的
Web3アプリ開発者 オブジェクト・ケイパビリティ(権限そのものをデータ化して渡す方式)を採用するプロトコルやAPIに対して、ReCapsを使って認証・権限委譲を統合した実装を行うために利用します。
プロトコル/API開発者 自分たちのサービス向けに、どの権限をどの書式でReCapsに載せるか(ReCap Details Objectの設計)を学ぶために利用します。
ウォレット実装者 署名時にユーザーへ分かりやすい同意画面を提供するため、ReCapsのUI表現を改善する目的で利用します。

「オブジェクト・ケイパビリティ」は、アクセス権限を「オブジェクト(JSONなどのデータ)」として表し、必要最小限の権限だけを相手へ安全に引き渡す考え方を指します。

用語と定義

用語 説明
ReCap ERC5573に準拠したSIWEメッセージです。SIWEのResourcesに少なくとも1つのReCap URIを含み、かつそのURIに対応する人間可読の「ReCap文(ReCap Statement)」をSIWEのstatement末尾に付与します。
ReCap URI ReCap Details Object(後述)へ解決されるURIです。urn:recap:で始まります。
ReCap Details Object どの「行為(アクション)」を、必要に応じてどの「対象リソース」に対して行えるかをJSONで表すオブジェクトです。
Resource Service(RS) Ethereumアカウントに対して第三者機能を提供する側のサービスです(例:ユーザーデータ保存サービス)。
SIWE Client(SC) SIWE認証とReCapフローを開始する側の主体です。
Relying Party(RP) 認可の文脈ではSCと同義で、ユーザーから権限を委ねられるサービス側のことです。

概要

ERC5573は、以下の要素を定義します。

  • ReCap SIWE拡張(SIWEメッセージの拡張)
  • ReCap Capability(権限オブジェクトの基本単位)
  • ReCap URIスキーム(URIの表現方法)
  • ReCap Details Objectスキーマ(JSON構造)
  • ReCap変換アルゴリズム(人間可読文への決定的変換)
  • ReCap検証(署名と文言一致の検証方法)

どれも「ユーザーが理解したうえで同意した範囲の権限だけを、RPが行使できる」ことを、決定論的に再現するための要素です。

ReCap SIWE拡張

ReCapは、特定書式に従うERC4361(SIWE)メッセージです。 ユーザーのEthereumアカウントが、RPに対してReCap Capabilities(権限集合)を明示的同意のもとで委譲します。 各CapabilityはSIWEメッセージのResources配列の最後の要素としてReCap URIで表現し、その内容はReCap変換アルゴリズムによって人間可読な文に決定論的(入力が同じなら必ず同じ出力)に変換され、statement末尾へ付与されます。

権限のスコープ(有効範囲)は、SIWE標準フィールドで制約します。

SIWEフィールド 役割
uri 権限を委ねる対象のRPを指します(例:https://example.com または DID(分散ID)did:key:...)。RSは、アクション実行時にこのRPであることを認証する想定です。
issuedAt 権限の発行日時です。
expirationTime (任意)この時刻以降は権限が無効になります。
notBefore (任意)この時刻までは権限が有効になりません。
SIWEメッセージ例
text
example.com wants you to sign in with your Ethereum account:
0x0000000000000000000000000000000000000000

I further authorize the stated URI to perform the following actions on my behalf: (1) 'example': 'append', 'read' for 'https://example.com'. (2) 'other': 'action' for 'https://example.com'. (3) 'example': 'append', 'delete' for 'my:resource:uri.1'. (4) 'example': 'append' for 'my:resource:uri.2'. (5) 'example': 'append' for 'my:resource:uri.3'.

URI: did:key:example
Version: 1
Chain ID: 1
Nonce: mynonce1
Issued At: 2022-06-21T12:00:00.000Z
Resources:
- urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbSI6eyJleGFtcGxlL2FwcGVuZCI6W10sImV4YW1wbGUvcmVhZCI6W10sIm90aGVyL2FjdGlvbiI6W119LCJteTpyZXNvdXJjZTp1cmkuMSI6eyJleGFtcGxlL2FwcGVuZCI6W10sImV4YW1wbGUvZGVsZXRlIjpbXX0sIm15OnJlc291cmNlOnVyaS4yIjp7ImV4YW1wbGUvYXBwZW5kIjpbXX0sIm15OnJlc291cmNlOnVyaS4zIjp7ImV4YW1wbGUvYXBwZW5kIjpbXX19LCJwcmYiOltdfQ

ReCap Capability

ReCap Capabilityは、1つのReCap URIで一意に識別されます。 URIを解決するとReCap Details Objectが得られ、そこに「どのアクションを、どのリソースに対して許可するか」が記されています。 さらに、SIWEのuri、issuedAt、expirationTime、notBeforeで、全Capabilityに共通する有効範囲を絞り込みます。

ReCap URIスキーム

ReCap URIは以下の形式です。

text
urn:recap:<unpadded-base64url(ReCap Details Object)>

base64urlはRFC 4648で定義されるURL安全なBase64です(+や/の代わりに-と_を使い、パディング=を省きます)。 ReCap URIはResources配列の最後に置く必要があります。

ReCap Capabilityの例
text
urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbS9waWN0dXJlcy8iOnsiY3J1ZC9kZWxldGUiOlt7fV0sImNydWQvdXBkYXRlIjpbe31dLCJvdGhlci9hY3Rpb24iOlt7fV19LCJtYWlsdG86dXNlcm5hbWVAZXhhbXBsZS5jb20iOnsibXNnL3JlY2VpdmUiOlt7Im1heF9jb3VudCI6NSwidGVtcGxhdGVzIjpbIm5ld3NsZXR0ZXIiLCJtYXJrZXRpbmciXX1dLCJtc2cvc2VuZCI6W3sidG8iOiJzb21lb25lQGVtYWlsLmNvbSJ9LHsidG8iOiJqb2VAZW1haWwuY29tIn1dfX0sInByZiI6WyJ6ZGo3V2o2Rk5TNHJVVWJzaUp2amp4Y3NOcVpkRENTaVlSOHNLUVhmb1BmcFNadUF3Il19

アビリティ文字列

アクションは<namespace>/<ability>の書式で表現します(例:crud/update)。 namespaceとabilityは英数字と. * _ + -のみ使用できます。 正規表現は以下のとおりです。

  • namespace と ability 個別の制約 ^[a-zA-Z0-9.*_+-]$(※仕様文面の意味としては「これらの文字のみ可」)
  • 全体 ^[a-zA-Z0-9.*_+-]+\/[a-zA-Z0-9.*_+-]+$

ReCap Details Object スキーマ

Details Objectは、RPがユーザーの代わりに呼び出せる行為(Ability)と、対象となるリソースURIを列挙します。 さらに、RS(リソースサービス)が検証時に必要とする追加情報も含められます。

json
{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "type": "object",
  "properties": {
    "att": {
      "type": "object",
      "propertyNames": { "format": "uri" },
      "patternProperties": {
        "^.+:.*$": {
          "type": "object",
          "patternProperties": {
            "^[a-zA-Z0-9.*_+-]+\\/^[a-zA-z0-9.*_+-]+$": { "type": "array", "items": { "type": "object" } }
          },
          "additionalProperties": false,
          "minProperties": 1
        }
      },
      "additionalProperties": false,
      "minProperties": 1
    },
    "prf": {
      "type": "array",
      "items": { "type": "string", "format": "CID" },
      "minItems": 1
    }
  }
}
  • att attenuations(制限指定)。 キーはリソースURI、値は「Ability文字列 → 制約配列」のオブジェクトです。 キーは辞書順(バイト値順、かつ前方一致は短い方を先)で並べます。
  • prf proofの略で、親ケイパビリティをBase58のCID(Content Identifier、コンテンツアドレス可能な識別子)で参照します。 SIWEの署名者(アドレス)がattのコントローラでない場合に、その正当性を証明するために使います。
  • 制約配列の中の空オブジェクト {} 「制限なしで実行可」を意味します。 空配列 [] 「有効な使い方が存在しない(実質的に不許可)**」を意味します。
Details Objectの例(attとprfあり)
json
{
  "att": {
    "https://example.com/pictures/": {
      "crud/delete": [{}],
      "crud/update": [{}],
      "other/action": [{}]
    },
    "mailto:username@example.com": {
      "msg/receive": [{
        "max_count": 5,
        "templates": ["newsletter", "marketing"]
      }],
      "msg/send": [{ "to": "someone@email.com" }, { "to": "joe@email.com" }]
    }
  },
  "prf": ["bafybeigk7ly3pog6uupxku3b6bubirr434ib6tfaymvox6gotaaaaaaaaa"]
}

この例では、https://example.com/pictures/ に対して crud/update・crud/delete・other/action が制限なく許可されます。 mailto:username@example.com に対しては、msg/sendは送信先を2件に限定し、msg/receiveは最大5件かつテンプレートがニュースレターまたはマーケティングに限定されています。

このDetails Objectは、ReCap変換アルゴリズムにより、SIWEのstatement末尾へ以下のような人間可読文に決定論的に変換されます(URIがhttps://example.comの場合の例)。

text
I further authorize the stated URI to perform the following actions on my behalf:
(1) 'crud': 'delete', 'update' for 'https://example.com/pictures/'.
(2) 'other': 'action' for 'https://example.com/pictures/'.
(3) 'msg': 'receive', 'send' for 'mailto:username@example.com'.

また、同じDetails Objectをunpadded base64urlでエンコードすると、以下のReCap URIになります。

text
urn:recap:eyJhdHQiOnsiaHR0cHM6Ly9leGFtcGxlLmNvbS9waWN0dXJlcy8iOnsiY3J1ZC9kZWxldGUiOlt7fV0sImNydWQvdXBkYXRlIjpbe31dLCJvdGhlci9hY3Rpb24iOlt7fV19LCJtYWlsdG86dXNlcm5hbWVAZXhhbXBsZS5jb20iOnsibXNnL3JlY2VpdmUiOlt7Im1heF9jb3VudCI6NSwidGVtcGxhdGVzIjpbIm5ld3NsZXR0ZXIiLCJtYXJrZXRpbmciXX1dLCJtc2cvc2VuZCI6W3sidG8iOiJzb21lb25lQGVtYWlsLmNvbSJ9LHsidG8iOiJqb2VAZW1haWwuY29tIn1dfX0sInByZiI6WyJ6ZGo3V2o2Rk5TNHJVVWJzaUp2amp4Y3NOcVpkRENTaVlSOHNLUVhmb1BmcFNadUF3Il19

ケイパビリティオブジェクトのマージ

2つのReCapオブジェクトは、フィールドを再帰的に結合することでマージできます(辞書順などの順序規則は維持する必要があります)。

入力
json
{
  "att": { "https://example1.com": { "crud/read": [{}] } },
  "prf": ["bafyexample1"]
}
json
{
  "att": {
    "https://example1.com": { "crud/update": [{ "max_times": 1 }] },
    "https://example2.com": { "crud/delete": [{}] }
  },
  "prf": ["bafyexample2"]
}
結果
json
{
  "att": {
    "https://example1.com": {
      "crud/read": [{}],
      "crud/update": [{ "max_times": 1 }]
    },
    "https://example2.com": { "crud/delete": [{}] }
  },
  "prf": ["bafyexample1", "bafyexample2"]
}

ReCap変換アルゴリズム

このアルゴリズムは、ReCap URIに埋め込まれたDetails Objectを人間可読な文に整形し、SIWEのstatement末尾に付与します。 決定論的であるため、同じ入力からは常に同じ文が得られます。

入力
  • recap-uri ReCap URI(中に適合するDetails Objectが入っていること)
  • statement 任意。 SIWEの既存statement(ある場合)
手順(擬似コードの流れ)
  1. 空文字列recap-transformed-statementを用意 既存statementがあるなら、それを連結し、続けてスペース1文字を追加します。
  2. 以下の前文を追加 I further authorize the stated URI to perform the following actions on my behalf:
  3. 番号numberingを1で初期化
  4. Details Objectのatt(制限指定)を走査(辞書順で) 各エントリからresource(対象URI)とabilities(アビリティ群)を得ます。
  5. abilitiesのキー(Ability文字列)を名前空間ごとにグルーピング 例:crud/deleteとcrud/update → 名前空間crudに属する2つのアビリティ。
  6. 各名前空間について、以下の文を生成して連結 (<numbering>) '<namespace>': '<ability1>', '<ability2>' for '<resource>'.
  7. numberingをインクリメントし、すべての名前空間・すべてのリソースについて繰り返す
  8. 完成したrecap-transformed-statementを返す

形式はABNFで規定されます(文字列や数値の定義はJSON(RFC 8259)準拠)。

ReCap検証アルゴリズム

RPとRSは、SIWEメッセージと署名が正当であり、さらにstatement末尾が変換アルゴリズムの出力と一致することを確認します。

入力
  • recap-siwe ERC5573に適合するSIWEメッセージ
  • siwe-signature recap-siweに対する署名(ERC4361準拠)
手順
  1. ERC4361の方式で、recap-siweとsiwe-signatureの署名検証を行う
  2. recap-siwe.uriを取得(RP識別用)
  3. recap-siwe.resourcesの最後の要素からReCap URIを取り出す
  4. そのReCap URIに含まれるDetails Objectとuriを入力として、ReCap変換アルゴリズムを実行し、recap-transformed-statementを得る
  5. recap-siwe.statementの末尾がrecap-transformed-statementであることを厳密に一致するか確認

これにより、「署名されたメッセージの内容」と「ReCap Details Objectが意味する権限文言」の齟齬を排除できます。 UIで見た文言=実際に署名した権限、という保証が得られます。

実装時の注意ポイント

  • 順序の決定性 att配下のキー(リソースURI、Ability文字列)は辞書順に並べます。 「バイト値順」、「前方一致は短い方が先」を満たすのは、JavaScriptのArray.sort()の既定と同じ挙動です。
  • 空オブジェクトと空配列の違い [{}]は「制限なしに実行可」、[]は「実行方法が存在しない(実質不可)」です。
  • CID(Content Identifier) prfに入るBase58エンコードのCIDは、上位の権限や委任の連鎖を証明する用途に使えます。
  • RSによるRP認証 uriで示したRPであることを、RS側が実行時に検証する前提です(DIDやHTTPSオリジンなどを使います)。
  • UI表現 ウォレットは、変換アルゴリズムが出力する決定論的な人間可読文をそのまま提示することで、ユーザーが「何を・どこに・どの範囲で」許可するのかを直感的に把握できる画面を実装できます。

セキュリティ

ベアラートークンとの違い

一般的なベアラートークンとは、トークンそのものを保持している者が正当なアクセス権を持つと見なされる仕組みです。 例えば、OAuth2などで発行されるアクセストークンが典型例です。 これらは、トークンを知っていれば誰でもリソースにアクセスできてしまうため、漏洩した場合のリスクが非常に高くなります。

一方、ReCapsは単なるトークンではなく、Ethereum署名を通じて権限を明示的に表した構造化データ(ケイパビリティ・オブジェクト)です。 ReCaps自体を第三者がコピーして利用できるようなものではなく、署名と整合性が取れていることが前提になります。 しかし、それでも「ReCapsの存在=アクセス権が自動的に認められる」という扱いはしてはいけません。

Relying Partyの認証が必要な理由

ReCapsは、ユーザーが特定のRelying Party(RP)に対して特定の行為を許可するための仕組みです。 そのため、リソースサービス(RS)は、ReCapsの内容に記されたRPが実際にそのRP本人(正当なクライアント)であることを確認する必要があります。

もしRSがRPの本人確認を行わず、ReCapsだけでアクセスを許可してしまうと、悪意のある第三者がReCapsの内容を不正に利用し、ユーザーの代わりに行動できてしまう恐れがあります。 したがって、ReCapsはあくまで「権限の内容を示す証明書」であり、本人確認の代替にはならないという点を理解しておくことが重要です。

認証方法は仕様の範囲外

ReCaps仕様自体では、Relying Partyをどのように認証するかは定義していません。 つまり、RPの認証方法は各リソースサービスの実装に委ねられています。

この実装方法は多様であり、以下のような手段が考えられます。

認証手段 概要
HTTPS/TLS証明書によるドメイン検証 RPのURIがHTTPSである場合、証明書を確認して正当なサーバーであることを保証します。
DID(分散型ID)認証 ReCapsのURIフィールドにDIDが使用されている場合、DID Documentに基づく署名検証を行います。
APIキーやOAuth 2.0トークンとの組み合わせ 追加のサーバー間認証を行い、RPが許可済みクライアントであることを確認します。

このように、ReCapsは「権限の定義」を担うものであり、「RPの正当性の証明」までは行いません。 したがって、セキュリティを確保するためには、RSが自ら適切な認証機構を設けることが不可欠です。

引用

Oliver Terbu (@awoie), Jacob Ward (@cobward), Charles Lehner (@clehner), Sam Gbafa (@skgbafa), Wayne Chang (@wyc), Charles Cunningham (@chunningham), "ERC-5573: Sign-In with Ethereum Capabilities, ReCaps [DRAFT]," Ethereum Improvement Proposals, no. 5573, July 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5573.

最後に

今回は「Sign-In with Ethereumの上に、ユーザーがどのサービスにどんな操作を許可するかを安全に指定できる仕組みを提案しているERC5573」についてまとめてきました! いかがだったでしょうか?

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

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