對於初學 IC 的開發者來說,Internet Identity(II)比較難以理解,所以我想寫幾篇文章盡可能簡潔的介紹清楚 II 的設計和實現。

作者:tinywateer,騰訊專家級工程師

封面:DFINITY

Internet Identity(II)整體上的一些設計

II 的設計目標主要是:

  1. 屏蔽掉私鑰/公鑰等技術概念,不讓用戶接觸它們,且更安全
  2. 用戶登錄不同的 DApp 有不同的身份,解決隱私問題
  3. 用戶登錄 DApp 有過期時間,在一段時間內有效

另外,一個必須的基礎是服務端永遠不保存私鑰

第一個目標,II 是如何設計的呢?主要是 Anchor、設備、助記詞  這三個方面。

a. 關於 Anchor。

Anchor 就是一個數字,也叫做 User Number。

Anchor 可以理解為用戶的 ID,它在 II 智能合約中是一個索引,可以根據 Anchor 查出來用戶的設備信息。

Anchor 是自增的。

b. 關於設備。

II 抽像出來了「設備」的概念,即 Anchor → 設備→ 私鑰/公鑰,有了設備這一層,用戶不需要去關注私鑰/公鑰,只要關注設備就足夠了。

設備的特點如下:

  1. 一個 Anchor 可以增加多個設備
  2. 每個設備都有自己獨立的私鑰/公鑰,私鑰保存在本地的設備,公鑰保存在服務端
  3. 每個設備都可以增加新的設備、刪除其他的設備

一般要對 Anchor 增加多個設備,在任何一個設備都可以登錄 IC 上的 DApp。

一個設備丟了,不會對使用造成影響,只需要在一台設備上刪除丟失的設備,然後增加新的設備即可。

設備一般是電腦或者手機上的瀏覽器,在使用時需要刷臉或者指紋(基於 WebAuthn 實現),外人很難盜用,更加安全。

c. 關於助記詞。

在極端情況下,所有設備都丟了,怎麼辦?

這時候,還有助記詞,用助記詞增加一個新的設備,然後把其他所有設備都刪除即可。

雖然助記詞底層有一對私鑰和公鑰,但是用戶可以把它看做是「恢復賬戶」的文本,不需要去理解私鑰和公鑰概念。

助記詞的公鑰也是保存在服務端,而私鑰不會保存在本地,當恢復時,使用助記詞生成私鑰,然後和服務端的公鑰做驗證,驗證通過即可恢復。

本質上,對於服務端來說,助記詞也是個「設備」(類型為 seed_phrase,用途為 recovery)。

這裡需要注意兩點:

  1. 設備和助記詞的「地位」其實是一樣的
    1. 在一台設備上,可以刪除和增加設備,也可以刪除和新建一個助記詞
    2. 用助記詞增加一台新設備後,可以做同樣的事情
  2. 要保證你的 Anchor 的設備和助記詞至少有一個存在,否則你的 Anchor 就無法恢復了,建議不要輕易修改助記詞

第二個目標,II 是如何設計的呢?這裡主要是身份。

身份,即 Identity,是 IC 的一個特有抽象,其實在我看來,身份本質上是用戶(Anchor)登錄到不同 DApp 時的一個「分身」。

身份是基於公鑰計算得來。

公鑰的計算方式是:

|ii_canister_id| · ii_canister_id · seed

身份的計算方式是:

SHA-224(Public-Key) · 0x02

其中 seed 計算方式是:

seed = SHA-256(|salt| · salt · |user_number| · user_number · |frontend_host| · frontend_host)

注:|...| 表示... 的長度,user_number 就是 Anchor,frontend_host 是 DApp URL。

可以看到,身份的決定因素是 Anchor 和 frontend_host(II Canister ID 是不變的)。

這就保證了:

  1. 當一個 Anchor 登陸不同 DApp 時,身份是不一樣的
  2. 在一個 Anchor 不同的設備上登陸 DApp,身份是一樣的

但是,有個比較嚴重的問題,由於目前大部分 DApp URL 中包含 Canister ID,當 Canister ID 被刪掉重建時,意味著 DApp 中的身份數據會發生變化!

第三個目標,II 設計了一套 Delegation 機制。

Delegation 機制是 II 中非常重要的設計,它本質是權限委託,它的基本原理是:

  1. 基於一對私鑰和公鑰(A)對另一對私鑰和公鑰(B)進行簽名,簽名之後, 後者可以代表前者的權限
  2. 簽名時可以帶上 Scope 和 Expiration,Scope 是作用範圍,也就是 frontend_host,Expiration 是過期時間,過期之後簽名失效

Delegation 可以套娃,不斷簽下去,形成 Delegation Chain。

Delegation 機制

在 II 中,A 是任何一個設備的私鑰和公鑰,B 是 DApp 前端臨時生成的私鑰和公鑰,B 的過期時間在 DAapp 前端設置。

簽名之後,就表示 DApp 前端臨時的私鑰和公鑰可以代表設備的私鑰和公鑰和 DApp 交互了,當然 DApp 前端也會獲得和 DApp 綁定的用戶的身份(Principal)。

另外:

  1. 簽名過程,首先要驗證設備的私鑰和公鑰,然後對   臨時的公鑰  做簽名,當然還會帶上 Anchor、frontend_host、Expiration,簽名結果會保存下來
  2. 如果驗證呢?在 DApp 前端向 IC 發送數據時,用臨時的私鑰加密,並且帶上簽名,IC 會驗證簽名的正確性,如果正確,就用臨時的公鑰解密,獲取數據

另外,II 的這套簽名機制有個叫法:Canister Signature,目前官方已經開放這個功能,不僅 II 可以使用,開發者開發 DApp 也可以使用了。

對 II 未來的展望

II 是 IC 生態 DApp 的單點登錄系統(SSO),如果 IC 最終做成功了,II 則是整個互聯網的單點登錄系統。

而且,在未來,II 可以直接簽發 Https 證書,也許會顛覆 Https 證書市場。

聊聊實現邏輯和核心代碼

本文主要包含三個部分:

  1. Anchor 和設備管理
  2. 如何生成身份(Principal)
  3. Delegation 實現邏輯

II 實現的函數如下:

II 的函數列表

前面部分是 Anchor 和設備管理,get_principal 是生成身份,prepare_delegation 和 get_delegation 是 Delegation 的實現。

第一部分,Anchor 和設備管理。

設備的數據結構:

設備的數據結構

DeviceData 主要包括 pubkey(公鑰)、alias(別名)、credential_id(憑據,可選,用於 WebAuthN 認證)、purpose(用途)、key_type(類型)。

設備分為普通設備和用於恢復的設備:

  1. 普通設備,purpose 為 authentication,key_type 為 unknown
  2. 用於恢復的設備,purpose 為 recovery,key_type 又分為兩種情況:
    1. 如果使用助記詞,key_type 為 seed_phrase
    2. 如果使用安全秘鑰,key_type 為 cross_platform

在第一次使用 II,創建 Anchor 時,會提示輸入第一個設備名稱,點擊 Create 後,會調用後端的 register 函數同時創建 Anchor 以及增加第一個設備。

創建 Anchor 和第一個設備
register 函數

register 幾個重要的點:

  1. check_entry_limits 是檢查一些限制,包括別名、公鑰和憑據的長度限制
  2. store.allocate_user_number() 會生成一個 Anchor,它是自增 ID,加 1 即可
  3. 然後把 Anchor 和設備信息保存下來

註冊 Anchor 之後,可以繼續增加設備,這時候調用的是 add 函數。

add 函數

add 函數需要注意的一點是,trap_if_not_authenticated 會驗證請求的合法性,驗證方法是檢查前端的身份(caller,通過設備公鑰生成)是不是在已知的設備身份列表裡。

trap_if_not_authenticated 不僅在設備的函數中做驗證,也會為身份和 Delegation 的函數做驗證。

具體代碼是:

trap_if_not_authenticated

當然,設備支持還支持刪除,這裡不再贅述。

第二部分,如何生成身份(Principal)。

在上篇文章中有說過,身份主要是根據 Anchor(user_number)和 frontend_host(frontend)生成。

get_principal 函數

check_frontend_length 是檢查 frontend 長度,不能超過 255 字節。

計算身份用到了 calculate_seed 和 der_encode_canister_sig_key 兩個函數,如下:

calculate_seed 和 der_encode_canister_sig_key 函數

這裡沒有特別要說的,略過。

第三部分,關於 Delegation 實現邏輯。

在 DApp 前端,選擇 II 登陸的時候,前端會生成一對私鑰和公鑰,這個私鑰和公鑰是臨時的,有過期時間。

然後會跳轉到 identity.ic0.app ,並顯示 DApp 的 URL,點擊 Proceed,會進行 Delegation 的處理,完成後,跳轉回 DApp URL。

II 跳轉確認

在點擊 Proceed 的時候前端會調用 prepare_delegation 和 get_delegation 兩個函數。

Delegation 數據結構
prepare_delegation 函數
get_delegation 函數

Delegation 的邏輯是在 prepare_delegation 中,get_delegation 只是前端重新獲取了一次。

前端傳給 prepare_delegation 的參數主要是:

  1. user_number:也就是 Anchor
  2. frontend:DApp URL
  3. session_key: 前端生成的臨時公鑰
  4. max_time_to_live: Delegation 過期時間,由前端指定,但是服務端有限制,最大 8 天(MAX_EXPIRATION_PERIOD_NS)

prepare_delegation 對這四者做一個簽名,並保存在服務端。

prepare_delegation 還會返回通過 user_number(Anchor)和 frontend 計算出來的公鑰,它代表著登陸到 DApp 的身份,前端會拿到這個身份,注意,這個身份和前端臨時生成的私鑰和公鑰沒有關係。

前端調用 get_delegation 之後,就可以獲得 Delegation 和它的簽名了。

所以,在 II 跳轉回 DApp URL 之後,前端就可以獲取到身份和 Delegation:

  1. 身份,代表著用戶登錄到 DApp 的「分身」
  2. Delegation 代表著經過設備授權的可以與 DApp 交互的權限

下面兩張圖是前端存儲裡保存的身份和 Delegation 例子。

前端的身份
前端的 Delegation

總結

附一張 II 的架構圖:

II 架構圖

另外下面這張圖描述了登陸 DApp 的流程:

II 登陸 DApp 流程圖

最後,II 的代碼地址,見這裡

講講前端部分

說到前端,主要是 Agent-JS,在 IC 上開發 DApp 必須要用到這個庫。

Agent-JS 簡單來說,是這樣:

  1. Ed25519:生成公私鑰對,實現 SignIdentity
  2. Auth Client:請求 II 生成 DelegationIdentity,它提供 transformRequest 方法進行簽名
  3. Http Agent :封裝 DelegationIdentity、提供 call、query、readState、status 通用函數
  4. Actor:封裝 IDL、Http Agent、Canister ID 等生成直接請求後端函數的 Actor

具體的代碼還是挺多的,這裡不一一列舉。

只講講 Auth Client 和 DelegationIdentity 的部分。

Auth Client 的核心代碼在   這裡,也可以看下面的圖。

可以看到,使用 Ed25519 生成一對臨時的公私鑰對,默認有效時間 8 小時,並綁定了_getEventHandler。

Auth Client 的 login 代碼

在_getEventHandler 中,如果認證成功,會執行_handleSuccess 函數,_handleSuccess 從返回的數據中提取 Delegation,從而生成 Identity。

_handleSuccess 函數

注意,服務端返回的 message.userPublicKey.buffer 是用戶的 Identity 對應的公鑰,不是臨時公鑰。

DelegationChain.fromDelegations 這部分代碼在   這裡,這也是核心代碼。

也可以看圖:

DelegationChain 函數

然後調用 DelegationIdentity.fromDelegation 傳入公私鑰對(key)、DelegationChain 生成 DelegationIdentity。

具體見:

DelegationIdentity 函數

注意,DelegationIdentity 中的 getPublicKey 返回的是用戶身份的公鑰,不是臨時公鑰。

DelegationIdentity 還提供了 sign 函數,通過臨時的私鑰對請求進行簽名,發送給後端。

具體的簽名動作是在 transformRequest 函數中,每次發送請求都會執行 transformRequest,後端會驗證 Delegation 是否正確。

transformRequest 中的 sender_pubkey 就是用戶身份的公鑰,在 DApp 中可以拿到並使用。

免責聲明:作為區塊鏈信息平台,本站所發布文章僅代表作者及嘉賓個人觀點,與 Web3Caff 立場無關。本文內容僅用於信息分享,均不構成任何投資建議及要約,並請您遵守所在國家或地區的相關法律法規。