1. 程式人生 > >Hyperledger Indy:一個使用 Libindy 構建 Indy 客戶端的開發指南

Hyperledger Indy:一個使用 Libindy 構建 Indy 客戶端的開發指南

Hyperledger Indy

Indy 和 Libindy 是什麼,能用來做什麼

Indy 為私有的、安全的並且有效的身份資訊提供了一個軟體生態圈,Libindy 提供了它的客戶端。Indy 將人 - 並不是傳統那種傳統的中心化管理的身份 - 能夠決定他們自己的身份資訊的隱私性和想要暴漏哪些資訊。這個帶來了前所未有的創新:連接合約(connection contracts),取消(revocation),新的支付工作流(novel payment workflows),資產(asset)和文件管理(document management)的功能,創新形式的第三方支付(creative forms of escrow),curated reputation,同其他技術的整合,以及其他的方面。

Indy 使用開源的分散式賬本技術。這些賬本是以資料庫的形式存在的,是由很多參與者來共同維護的,而不是一箇中心化管理的巨大的資料庫。資料會冗餘地儲存在很多地方,並且有很多臺電腦一起協作來進行更新。強健的,業界標準的加密技術為其提供保護。金鑰管理及網路安全的最佳實踐貫穿了它的設計。這麼做的結果就是產生了這樣一個可信賴的,公共的事實來源,這不被任何單一的個人來管控,具有穩定的系統失敗處理,對黑客攻擊的快速恢復,以及對惡意的實體的破壞的高免疫性。

如果對於加密和區塊鏈的詳細的概念感覺很難理解,不要害怕:這個開發指南會幫助給你介紹 Indy 中的核心概念,你來對地方了。

我們會講些什麼

我們的目標是向你介紹很多關於 Indy 的概念,幫助你來理解讓這一起工作起來的背後的原因。

我們會將整個過程編為一個故事。Alice,一個虛構的 Faber 大學的畢業生,想要應聘一家虛構的公司 Acme Corp 的一份工作。當她獲得了這份工作後,她想要向 Thrift 銀行申請一筆貸款,這樣她就可以購買一輛汽車了。在工作申請表單上,她想用她的大學成績單作為受過教育證明,並且一旦被錄用後,Alice 想使用被僱傭的事實來作為申請貸款的信譽憑證。

在當今的世界裡,身份資訊以及信任互動非常混亂,它們很慢,與隱私性相違背,容易受到欺詐。我們將會展示給你 Indy 是如何讓這些產生了巨大的進步。

關於 Alice

作為 Faber 大學的畢業生,Alice 收到了一封畢業生的 newsletter,從中瞭解到她的母校可以提供數字成績單(digital transcripts)。她登入了學校的畢業生網站,通過點選 獲得成績單

按鈕,她申請了自己的成績單。(其他的發起這個請求的方式還可能包括掃描一個 QR code,從一個公共的 URL 下載一份打包的成績單,等等)

Alice 還沒有意識到,想要使用這個數字的成績單,她需要一個新的型別的身份資訊 - 並不是 Faber 大學儲存在在校(on-campus)資料庫中為她建立的傳統的身份資訊,而是一個屬於她自己的全新的便攜的身份資訊,獨立於所有的過去和將來的關係,不經過她的允許,沒有人能夠廢除(revoke)、指派(co-opt)或者關聯(correlate)這個身份資訊。這就是一個 自我主權的身份資訊(self-sovereign identity),也是 Indy 的核心功能。

在常規的情況下,管理一個自我主權的身份資訊會要求使用 一個工具,比如一個桌面的或者手機的應用程式。它可能是一個獨立的應用,或者使用一個第三方的服務機構(代理商)提供的賬本服務。Sovrin 基金會(Sovrin Foundation)釋出了一個這種型別的工具。Faber 大學瞭解了這些需求,並會建議 Alice 安裝一個 Indy app 如果她沒有安裝過的話。這個 app 會作為點選 獲得成績單 按鈕之後的工作流中的一部分而被安裝。

當 Alice 點選了 獲得成績單 按鈕後,她會下載一個帶有一個 Indy 連線請求 的檔案。這個連線請求檔案的副檔名為 .indy,並且會和 Alice 的 Indy app 相關聯,將會允許 Alice 建立跟另外一個在這個賬本生態圈(ledger ecosystem)存在的一方(Faber 大學)的一個安全的資訊溝通頻道(channel)。

所以當 Alice 點選了 獲得成績單 的時候,通常她會以安裝一個 app(如果需要的話)結束,開啟這個 app,接著會被這個 app 詢問她是否接受一個同 Faber 大學建立連線的請求。

在這個開發指南中,我們會使用一個 Indy SDK API (由 Libindy 提供的),而不是使用一個 app,這樣我們就能知道在程式的背後執行著什麼。

基礎設施準備 Infrastructure Preparation

第一步:獲得為 Faber 大學、Acme 公司、Thrift 銀行及政府的 Trust Anchor Credentials

Faber 大學和其他的演員已經做了一些準備來為 Alice 提供這些服務。為了理解這些步驟,我們先來看看一些定義。

賬本的目的是用來儲存一些 身份記錄(Identity Records) ,這些記錄描述了一個 賬本實體(Ledger Entity)。身份記錄是公共的資訊並可能會含有公鑰(Public Keys)、服務的 Endpoints、Credential Schemas 和 Credential Definition。每一個 Identity Records 會關聯到一個精確的 DID(去中心化的身份編碼 Decentralized Identifier),這個識別碼是全球唯一的,並且能夠被賬本來使用而不需要任何中心化的處理機構授權。為了維護隱私性,每個 身份所有者(Identity Owner) 可以擁有多個 DIDs。

在這個教程中,我們會使用兩種型別的 DIDs。第一種型別是 Verinym。一個 Verinym 會關聯到 Identity Owner法律身份(Legal Identity)。比如所有的參與者應該能夠驗證一些政府使用的 DIDs 是用來發布一些關於某些檔案型別的 schemas。第二種型別是 Pseudonym - 一個 Blinded Identifier 被用來維護在持續的數字化關係(Connection)上下文中的隱私性。如果 Pseudony 僅僅被用來維護一個數字化的關係,我們會叫它 Pairwise-Unique Identifier。在這個教程中,我們會使用 Pairwise-Unique Identifier 來在演員之間維護一個安全連線。

建立一個賬本可以識別的 DID 是一個 Identity Record 本身(NYM transaction)。NYM transaction 可以被用來建立一個新的賬本可識別的 DID,和對於一個驗證金鑰(verification key)的設定以及輪循(rotation),以及對角色的設定和變動。這個 transaction 的最關鍵的一個欄位是 dest (目標 DID),role (NYM 記錄將要建立的一個使用者的角色) 以及 verkey (目標驗證金鑰 verification key)。

釋出時帶有一個 DID 驗證金鑰會允許一個使用者、組織或者任何事情來驗證某個人是這個 DID 的所有者,因為他們是唯一的一個知道相應的簽名金鑰以及任何需要使用該金鑰簽名的 DID 有關的操作的那個個人、組織或者事情。

我們的賬本是公開並受限(public permissioned)的,任何想要釋出 DIDs 的參與者需要取得賬本上的 Trust Anchor 的角色。一個 Trust Anchor 是一個賬本已經知道的個人或者組織,這個有助於啟動其他的個人或組織。(這個跟網路安全專家所說的“授信的第三方”是不同的,更應該把它想象為一個協調者 facilitator)。

想要能夠在賬本上儲存 transactions 的第一步是獲得在賬本上 Trust Anchor 的角色。Faber 大學,Acme Corp 公司和 Thrift 銀行需要獲取到在賬本上的 Trust Anchor 的角色,所以他們就能夠建立 Verinyms 和 Pairwise-Unique Identifiers 並向 Alice 提供相關的服務。

想要成為一個 Trust Anchor 需要聯絡一個人或者組織,這個人或者組織必須在賬本上已經是一個 Trust Anchor 的角色了。在這個 demo 中,在我們的空的測試賬本里,我們只有帶有 Steward 角色的 NYMs,然而所有的 Stewards 都會自動會是 Trust Anchors 的角色。

第二步:連線 Indy Nodes Pool

我們已經準備好了要編寫程式碼來從頭到尾地處理 Alice 的用例了。有一點很重要,為了 Demo 的目的,這只是一個包含在不同的 agents 之間可執行的程式碼的測試。我們會指定針對每段程式碼應該是哪個 agent 來執行。我們也會使用不同的錢包來儲存不同 Agents 的 DID 和 金鑰。讓我們開始吧。

第一段程式碼塊會包含 Sweward 的 agent 的程式碼。

在獲得了合適的角色之後,想要寫和讀取賬本上 transactions 的話,你需要跟 Indy nodes pool 建立連線。為了跟已經存在的那些不同的 nodes pools 進行連線(比如 Sovrin pool 或者我們為了這個教程建立的本地的 pool),你需要設定一個 pool configuration。

Nodes pool 中都有哪些節點在賬本中是以 NODE transactions 的形式儲存的。Libindy 允許你通過幾個已知的 transactions 來獲得實際的 NODE transactions 列表,這些已知的 transactions 被稱為創世交易(genesis transactions)。每個 Pool Configuration 都是以一個 pool configuration 名字和 pool configuration JSON 的對兒(pair)的形式定義的。在 pool configuration json 中最重要的欄位是包含 genesis transactions 列表的檔案路徑。請確保這個路徑是正確的。

pool.create_pool_ledger_config 呼叫允許你能夠建立一個已命名的 pool configuration。在 pool configuration 被建立好之後,我們可以通過呼叫 pool.open_pool_ledger 來連線在這個 configuration 裡提到的 nodes pool。這個呼叫會返回 pool handle,可以在之後的 libindy 呼叫中引用這個開啟的連線(pool handle)。

下邊的程式碼包含了這些專案。注意程式碼中的註釋說明了這個程式碼是針對“Steward Agent”的。

# Steward Agent
  pool_name = 'pool1'
  pool_genesis_txn_path = get_pool_genesis_txn_path(pool_name)
  pool_config = json.dumps({"genesis_txn": str(pool_genesis_txn_path)})
  await pool.create_pool_ledger_config(pool_name, pool_config)
  pool_handle = await pool.open_pool_ledger(pool_name, None)

第三步:獲得 Steward’s Verinym 的 ownership

接下來,Steward agent 應該取得在賬本上以 Steward 角色記錄的擁有對應的 NYM transactions 的 DID 的 ownership。

我們使用的測試賬本已經被提前配置好的,它儲存了一些已知的 Steward NYMs。並且我們知道被用來對這些 NYMs 生成金鑰的隨機數生成器的 seed 值。這些 seed 值允許我們能夠在 Steward agent 這邊為這些 DIDs 恢復簽名金鑰(signing keys),以此來獲得 DID 的 ownership。

Libindy 有一個 錢包(Wallet) 的概念。錢包是安全的儲存諸如DIDs,金鑰等加密資料的地方。為了儲存了 Steward 的 DID 和對應的 signkey,agent 應該通過呼叫 wallet.create_wallet 建立一個已命名的錢包。這步之後這個已命名的錢包可以通過呼叫 wallet.open_wallet 來開啟這個錢包。這次呼叫會返回錢包的 handle,它可以在之後的 libindy 呼叫中可以被用來引用這個開啟的錢包。

當錢包被開啟之後,我們可以通過呼叫 did.create_and_store_my_did 來在錢包中建立一個 DID 記錄,這會返回生成的 DID 和 生成的金鑰的 verkey 部分。這個 DID 的 signkey 部分也會被儲存在錢包中,但是我們是不能直接讀取它的。

  # Steward Agent
  steward_wallet_name = 'sovrin_steward_wallet'
  await wallet.create_wallet(pool_name, steward_wallet_name, None, None, None)
  steward_wallet = await wallet.open_wallet(steward_wallet_name, None, None)

  steward_did_info = {'seed': '000000000000000000000000Steward1'}
  (steward_did, steward_key) = await did.create_and_store_my_did(steward_wallet, json.dumps(steward_did_info))

注意:我們僅僅向 did.create_and_store_my_did 提供了關於 seed 的資訊,但是沒有提供任何關於 Steward 的 DID 資訊。預設生成的 DID 是 verkey 的前16個位元組。對於這種 DID,當我們處理既需要 DID 又需要 verkey 的操作的時候,我們可以以縮寫的方式來使用 verkey。在這種形式中,verkey 以波浪字元 “~” 開始,接下來是 22 或者 23 個字元。這個波浪線表示這個 DID 本身代表了 verkey 的前16個位元組,在波浪線後邊的字串代表了 verkey 的第二個 16 個位元組,他們兩個都使用了 base58Check 編碼。

第四步:通過 Steward 將 Faber 大學,Acme 公司,Thrift 銀行和政府 Onboarding

現在 Faber 大學、Acem 公司、Thrift 銀行以及政府應該同 Steward 建立連線

每個連線實際上應該是一個 Pairwise-Unique Identifiers(DIDs)對(pair)。一個 DID 是由與之連線的一方所有,第二個應該是另一方所有。

雙方都知道兩個 DIDs 並且知道這個對兒描述的是什麼連線。

他們之間的關係是不會共享給其他人的,對於這兩方來說,每一個 pairwise 關係都會使用不同的 DIDs,並且每個 DID 都是唯一的。

我們將建立連線的這個過程稱為 Onboarding

在這個教程中,我們會講述一個簡單版本的 onboarding 流程。在我們的例子中,一方將會一直是 Trust Anchor。真正的企業版本會使用更復雜的版本。

連線設施 Connecting the Establishment

讓我們看看連線 StewardFaber 大學 的設施的流程。

  1. FaberSteward 通過某種方式聯絡了要初始化一個 onboarding 的流程。這可以通過填寫一個網站上的表單或者是一個電話。
  2. Steward 通過呼叫 did.create_and_store_my_did 在它的錢包中建立了一個新的 DID,並且這個 DID 僅僅會被用於同 Faber 之間的互動。
# Steward Agent
(steward_faber_did, steward_faber_key) = await did.create_and_store_my_did(steward_wallet, "{}")
  1. Steward 將對應的 NYM transaction 傳送到賬本中,通過呼叫 ledger.build_nym_request 建立 NYM 請求,通過呼叫 ledger.sign_and_submit_request 來發送新建立的請求。
# Steward Agent
nym_request = await ledger.build_nym_request(steward_did, steward_faber_did, steward_faber_key, None, role)
await ledger.sign_and_submit_request(pool_handle, steward_wallet, steward_did, nym_request)
  1. Steward 建立了一個包含新建的 DIDNonce 的連線請求。這個 nonce 只是一個生成的大的隨機數,用來追蹤唯一的連線請求。一個 nonce 是一個任意的隨機數,並且它還只能被使用一次。當一個連線請求被接受的時候,被邀請的一方數字化地給這個 nonce 提供一個簽名,這樣邀請的發出者就可以將收到的反饋跟之前的請求相匹配了。
# Steward Agent
connection_request = {
    'did': steward_faber_did,
    'nonce': 123456789
}
  1. *Steward 將連線請求傳送給 Faber
  2. Faber 接受了來自於 Steward 的連線請求。
  3. 如果 Faber 之前沒有一個錢包的話,Faber 會建立一個錢包
# Faber Agent
await wallet.create_wallet(pool_name, 'faber_wallet', None, None, None)
faber_wallet = await wallet.open_wallet('faber_wallet', None, None)
  1. Faber 通過呼叫 did.create_and_store_my_did 在它的錢包裡建立了一個新的 DID,這個 DID 僅僅會被用於同 Steward 進行安全的互動。
# Faber Agent
(faber_steward_did, faber_steward_key) = await did.create_and_store_my_did(faber_wallet, "{}")
  1. Faber 建立了一個連線反饋(connection response),這個反饋包含了來自於接收到的連線請求的新建立的 DIDVerkeyNonce
# Faber Agent
connection_response = json.dumps({
    'did': faber_steward_did,
    'verkey': faber_steward_key,
    'nonce': connection_request['nonce']
})
  1. Faber 通過呼叫 did.key_for_did 來向賬本請求 Steward 的 DID 的 Verification key。
# Faber Agent
steward_faber_verkey = await did.key_for_did(pool_handle, faber_wallet, connection_request['did'])
  1. Faber 使用 Steward verkey 並呼叫 crypto.anon_crypt 將連線的 response 進行了匿名的加密。這種匿名加密 schema 被設計用來向接收者傳送訊息,這個接收者已經提供了它的公鑰。只有接收者才能夠使用它的私鑰來解密這些訊息。儘管接收者能夠確認訊息的一致性,但是它卻不能夠確認傳送者的身份。
# Faber Agent
anoncrypted_connection_response = await crypto.anon_crypt(steward_faber_verkey, connection_response.encode('utf-8'))
  1. Faber 將匿名加密過的連線 response 傳送給 Steward
  2. Steward 通過呼叫 crypto.anon_decrypt 匿名地將這個連線 response 進行解密。
# Steward Agent
decrypted_connection_response = \
    (await crypto.anon_decrypt(steward_wallet, steward_faber_key, anoncrypted_connection_response)).decode("utf-8")
  1. Steward 通過對比 Nonce 來對 Feber 進行授權。
# Steward Agent
assert connection_request['nonce'] == decrypted_connection_response['nonce']
  1. StewardFaber 的 DID 的 NYM transaction 傳送給賬本。請注意,儘管 Steward 是這個 transaction 的傳送方,但是這個 DID 的 owner 將會是 Faber 因為他所使用的 verkey 是由 Faber 提供的。
# Steward Agent
nym_request = await ledger.build_nym_request(steward_did, decrypted_connection_response['did'], decrypted_connection_response['verkey'], None, role)
await ledger.sign_and_submit_request(pool_handle, steward_wallet, steward_did, nym_request)

到目前為止,Faber 已經跟 Steward 建立了連線,並且可以以一種安全的 peer-to-peer 的方式與其進行互動。Faber 可以相信從 Steward 發過來的 response,因為:

  • 它連線的是當前的 endpoint
  • 沒有 replay - attack 是可能的,由於它的隨機數挑戰
  • 它知道用來確認 Steward 的數字簽名的確認金鑰(verification key)是一個正確的金鑰,因為它剛剛在賬本上確認了這個

注意:所有的參與方都不可以將相同的 DID 應用於建立其他的連線。通過具有獨立的 pairwise 關係,你減小了其他人通過跨多個互動來關聯你的行為活動的能力。

獲得 Verinym

理解之前建立的 Faber DID 同自主權的身份(self-sovereign identify)是不同的是非常重要的。這個 DID 必須只能用於同 Steward 進行安全的互動。當這個連線被建立起來之後,Faber 必須要建立一個新的 DID 記錄,這個 DID 會被用作賬本中 Verinym 來使用。

  1. Faber 通過呼叫 did.create_and_store_my_did 在它的錢包中建立了一個新的 DID。
# Faber Agent
(faber_did, faber_key) = await did.create_and_store_my_did(faber_wallet, "{}")
  1. Faber 準備了一個將要包含這個新建立的 DID 以及 verkey 的訊息。
# Faber Agent
faber_did_info_json = json.dumps({
    'did': faber_did,
    'verkey': faber_key
})
  1. Faber 通過呼叫 crypto.auth_crypt 方法對訊息進行了授權及加密,這個是對 authenticated-encryption schema 的一個實現。被授權的加密被設計用來向指定的接收者傳送一個對接收者比較特別的機密的訊息。傳送者可以使用接收者的公鑰(verkey)和它的金鑰(signing)計算出一個共享的安全金鑰。接收者使用傳送者的公鑰(verkey)和它的金鑰(signing)能夠計算出完全一致的共享的安全金鑰。這個共享的金鑰(secret key)可以在最終解密前可以用來驗證被加密過的訊息並沒有被篡改過。
# Faber Agent
authcrypted_faber_did_info_json = \
    await crypto.auth_crypt(faber_wallet, faber_steward_key, steward_faber_key, faber_did_info_json.encode('utf-8'))
  1. Faber 將加密過的訊息傳送給 Steward
  2. Steward 通過呼叫 crypto.auth_decrypt 將接收到的訊息進行解密。
# Steward Agent    
sender_verkey, authdecrypted_faber_did_info_json = \
    await crypto.auth_decrypt(steward_handle, steward_faber_key, authcrypted_faber_did_info_json)
faber_did_info = json.loads(authdecrypted_faber_did_info_json)
  1. Steward 通過呼叫 did.key_for_did 來向賬本請求 Faber 的 DID 的確認金鑰(verification key)。
# Steward Agent    
faber_verkey = await did.key_for_did(pool_handle, from_wallet, faber_did_info['did'])
  1. Steward 通過對比訊息傳送者的 Verkey 和從賬本中獲得的 Faber 的 Verkey 來給 Faber 授權(相同的情況下)。
# Steward Agent    
assert sender_verkey == faber_verkey
  1. Steward 會以 TRUST ANCHOR 的角色將對應的 NYM transaction 傳送給賬本。注意:雖然 Steward 是這個 transaction 的傳送方,但是 DID 的 owner 還是 Feber 因為 Verkey 是由 Faber 提供的。
# Steward Agent
nym_request = await ledger.build_nym_request(steward_did, decrypted_faber_did_info_json['did'],
                                             decrypted_faber_did_info_json['verkey'], None, 'TRUST_ANCHOR')
await ledger.sign_and_submit_request(pool_handle, steward_wallet, steward_did, nym_request)

到目前為止,Faber 在賬本中有一個同它的 identity 有關係的一個 DID。

Acem 公司Thrift 銀行, 和政府必須要通過相同的 onboarding 流程同 Steward 建立起連線。

第五步:設定 Credential Schemas

Credential Schema 是基礎的 semantic structure,它描述了一個特定的 Credential 可以包含的屬性列表。

注意:更新一個已經存在的 Schema 是不可能的。所以如果 Schema 需要更新,一個具有新的版本或者名字的新 Schema 需要被建立。

一個 Credential Schema 可以被任何的 Trust Anchor 建立並存儲到賬本中。

下邊就是 政府(Government) 是如何建立和釋出成績單的 Credential Schema 到賬本中的:

  1. Trust Anchor 通過呼叫 anoncreds.issuer_create_schema 來建立一個 Credential Schema,這個會返回新生成的 Credential Schema
# Government Agent
(transcript_schema_id, transcript_schema) = \
    await anoncreds.issuer_create_schema(government_did,
                                         'Transcript',
                                         '1.2',
                                         json.dumps(['first_name', 'last_name', 'degree', 'status', 'year', 'average', 'ssn']))
  1. Trust Anchor 通過呼叫 ledger.build_schema_request 來建立 Schema 請求,通過 ledger.sign_and_submit_request 來發送新建立的請求,以此將對應的 Schema transaction 傳送給賬本。
# Government Agent
schema_request = await ledger.build_schema_request(government_did, transcript_schema)
await ledger.sign_and_submit_request(pool_handle, government_wallet, government_did, schema_request)

用同樣的方式,政府建立併發布了 Job-Certificate Credential Schema 到賬本中:

  # Government Agent
    (job_certificate_schema_id, job_certificate_schema) = \
        await anoncreds.issuer_create_schema(government_did,
                                             'Job-Certificate',
                                             '0.2',
                                             json.dumps(['first_name', 'last_name', 'salary', 'employee_status', 'experience']))
  schema_request = await ledger.build_schema_request(government_did, json.dumps(to the Ledger))
  await ledger.sign_and_submit_request(pool_handle, government_wallet, government_did, schema_request)

到目前為止,由政府釋出的 成績單(Transcript)工作證明(Job-Certificate) Credential Schemas 已經被髮布在了賬本上。

第六步:設定 Credential Definition

Credential Definition 很類似於:發行使用者用來為 Credential 提供簽名的金鑰也需要滿足一個特定的 Credential Schema。

注意:想要更新一個已經存在的 Credential Definition 中的資料是不可能的。所以如果一個 CredDef 需要被更新的時候(比如一個金鑰需要被調換),那麼一個新的 Credential Definition 需要由一個新的發行方 DID 來建立。

一個 Credential Definition 可以被任何 Trust Anchor 建立並儲存到賬本中。這裡,Faber 大學建立併發布了一個有關已知的 Transacript Credential Schema 的 Credential Definition 到賬本中。

  1. Trust Anchor 從賬本中獲得指定的 Credential Schema。通過呼叫 ledger.build_get_schema_request 來建立 GetSchema 請求,ledger.sign_and_submit_request 來發送新建立的請求,ledger.parse_get_schema_responseGetSchema response 中獲得由 Anoncreds API 要求的格式的 Schema
# Faber Agent
get_schema_request = await ledger.build_get_schema_request(faber_did, transcript_schema_id)
get_schema_response = await ledger.submit_request(pool_handle, get_schema_request) 
(transcript_schema_id, transcript_schema) = await ledger.parse_get_schema_response(get_schema_response)
  1. Trust Anchor 通過呼叫 anoncreds.issuer_create_and_store_credential_def 的方式建立與接收到的 Credential Schema 相關的 Credential Definition,這會返回生成的公共的 Credential Definition。對於這個 Credential Schema 的私有的 Credential Definition 部分也將會被儲存在錢包中,但是是不可能直接讀取出來的。
# Faber Agent
(faber_transcript_cred_def_id, faber_transcript_cred_def_json) = \
    await anoncreds.issuer_create_and_store_credential_def(faber_wallet, faber_did, transcript_schema, 'TAG1', 'CL', '{"support_revocation": false}')
  1. Trust Anchor 將對應的 CredDef transaction 傳送到賬本中。通過呼叫 ledger.build_cred_def_request 來建立 CredDef 請求,呼叫 ledger.sign_and_submit_request 傳送建立的請求。
# Faber Agent     
cred_def_request = await ledger.build_cred_def_request(faber_did, faber_transcript_cred_def_json)
await ledger.sign_and_submit_request(pool_handle, faber_wallet, faber_did, cred_def_request)

同樣的方式,Acem 公司 為這個已知的 Job-Certificate Credential Schema 建立併發布了一個 Credential Definition 到賬本中。

# Acme Agent
  get_schema_request = await ledger.build_get_schema_request(acme_did, job_certificate_schema_id)
  get_schema_response = await ledger.submit_request(pool_handle, get_schema_request)
  (job_certificate_schema_id, job_certificate_schema) = await ledger.parse_get_schema_response(get_schema_response)

  (acme_job_certificate_cred_def_id, acme_job_certificate_cred_def_json) = \
      await anoncreds.issuer_create_and_store_credential_def(acme_wallet, acme_did, job_certificate_schema, 'TAG1', 'CL', '{"support_revocation": false}')

    cred_def_request = await ledger.build_cred_def_request(acme_did, acme_job_certificate_cred_def_json)
    await ledger.sign_and_submit_request(pool_handle, acme_wallet, acme_did, cred_def_request)

到目前為止,我們已經有了一個由 Acem 公司 釋出的關於 Job-Certificate Credential Schema 的 Credential Definition,和一個由 Faber 大學 釋出的有關 Transcript Credential Schema 的 Credential Definition

Alice 得到了成績單(Transcript)

一個 credential 是有關一個身份的部分資訊 - 一個名字,年齡,信用積分…這些資訊都應該是真實的。在我們的例子中,這個 credential 是已經有了名字的,叫做“成績單”。

Credential 是由一個發行方提供的。

一個發行方可以是對於賬本已知的任何的身份的所有者(identity owner),並且任何的發行方可以發行他能夠識別的任何的有關身份所有者的一個 credential。

對於一個 credential 的有效性和可靠性取決於這個發行方的名聲,和在它手中的 credential。對於 Alice 而言,如果她想自己發行一個 credential 的話,如果她發行的是她喜歡的巧克力冰淇淋的話,可能還說得過去,但是如果他自己發行了她是從 Faber 大學畢業的這個 credential 的話,沒有人會相信這個。

就像我們在關於 Alice 中所描述的那用,Alice 是從 Faber 大學 畢業的。當 Faber 大學 同 Alice 建立起了一個連線之後,它為她建立了一個發行 成績單 Credential 的一個 Credential Offer。

# Faber Agent
  transcript_cred_offer_json = await anoncreds.issuer_create_credential_offer(faber_wallet, faber_transcript_cred_def_id)

注意:在所有演員之間所傳遞的訊息都是使用 Authenticated-encryption schema 被加密過的。

這個 成績單 Credential 的價值在於它是由 Faber 大學 發行的。

Alice 想要檢視這個 成績單 Credential 所包含的屬性。這些屬性是已知的,因為有關 成績單 的 Credential Schema 已經被寫到賬本中了。

 # Alice Agent
  get_schema_request = await ledger.build_get_schema_request(alice_faber_did, transcript_cred_offer['schema_id'])
  get_schema_response = await ledger.submit_request(pool_handle, get_schema_request)
  transcript_schema = await ledger.parse_get_schema_response(get_schema_response)

  print(transcript_schema['data'])
  # Transcript Schema:
  {
      'name': 'Transcript',
      'version': '1.2',
      'attr_names': ['first_name', 'last_name', 'degree', 'status', 'year', 'average', 'ssn']
  }

然而,成績單 Credential 並沒有以一種可使用的方式傳送給 Alice。Alice 想要使用這個 Credential。為了得到它,Alice 需要請求它,但首先她必須要建立一個 Master Secret

注意:一個 Master Secret 是一個供證明人使用的關於私有資料的 item,用來保證一個 credential 能夠唯一地應用於自己。Master Secret 是一個 input,這個 input 合併了來自於多個 Credentials 的資料,用來證明這些 Credentials 有一個通用的主題(common subject)(證明者)。一個 Master Secret 應該只有證明者自己知道。

Alice 在她的錢包中建立 Master Secret。

# Alice Agent
  alice_master_secret_id = await anoncreds.prover_create_master_secret(alice_wallet, None)

Alice 還需要得到對應於在 成績單 Credential Offer 中的 cred_def_id 的 Credential Definition。

# Alice Agent
  get_cred_def_request = await ledger.build_get_cred_def_request(alice_faber_did, transcript_cred_offer['cred_def_id'])
  get_cred_def_response = await ledger.submit_request(pool_handle, get_cred_def_request)
  faber_transcript_cred_def = await ledger.parse_get_cred_def_response(get_cred_def_response)

現在,Alice 已經有了用於建立一個有關發行 Faber 大學成績單 Credential 的請求的所有資訊。

 # Alice Agent
    (transcript_cred_request_json, transcript_cred_request_metadata_json) = \
        await anoncreds.prover_create_credential_req(alice_wallet, alice_faber_did, transcript_cred_offer_json,
                                                     faber_transcript_cred_def, alice_master_secret_id)

Faber 大學 為這個 成績單 Credential Schema 中的每個屬性準備 raw 和 encoded 值。Faber 大學 為 Alice 建立了 成績單 Credential。

  # Faber Agent
  # note that encoding is not standardized by Indy except that 32-bit integers are encoded as themselves. IS-786
  transcript_cred_values = json.dumps({
      "first_name": {"raw": "Alice", "encoded": "1139481716457488690172217916278103335"},
      "last_name": {"raw": "Garcia", "encoded": "5321642780241790123587902456789123452"},
      "degree": {"raw": "Bachelor of Science, Marketing", "encoded": "12434523576212321"},
      "status": {"raw": "graduated", "encoded": "2213454313412354"},
      "ssn": {"raw": "123-45-6789", "encoded": "3124141231422543541"},
      "year": {"raw": "2015", "encoded": "2015"},
      "average": {"raw": "5", "encoded": "5"}
  })

  transcript_cred_json, _, _ = \
      await anoncreds.issuer_create_credential(faber_wallet, transcript_cred_offer_json, transcript_cred_request_json,
                                               transcript_cred_values, None, None)

現在,成績單 Credential 已經被髮行出來了。Alice 將它儲存在了自己的錢包中。

 # Alice Agent
  await anoncreds.prover_store_credential(alice_wallet, None, transcript_cred_request_json, transcript_cred_request_metadata_json,
                                          transcript_cred_json, faber_transcript_cred_def, None)

Alice 將它變為了自己的財產,這就像是她持有一張郵寄給她的紙質的成績單一樣。

申請工作

在將來的某個時間,Alice 希望能夠為虛擬出來的一家叫 Acme Corp 公司工作。通常來說,Alice 會瀏覽公司的網站,在網站中應該會有一個申請工作的連結。Alice 的瀏覽器會下載一份連線請求,在那裡她的 Indy app 將會開啟,這個會觸發向 Alice 彈出一個視窗,讓她來確認接受同 Acme Corp 的連線。因為我們在使用 Indy-SDK,那麼流程就有些不同了,但是步驟還是一樣的。建立連線的流程跟 Faber 大學接受 Steward 的連線請求的流程是一樣的。

當 Alice 與 Acme 公司建立了連線後,她會收到 Job-Application Proof 請求。一個 proof 請求是由一個需要某個證明的一方提出的,這個證明可以是證明具有某些屬性,並且還可以得到其他已經驗證(verified)的 credentials 的證明。

在我們的例子中,Acme Corp 要求 Alice 提供一個 工作申請表(Job Application)。這個申請表中要求一個名字、學歷、狀態、SSN 和是否滿足在校平均分數的一些條件。

Job-Application Proof Request 就像下邊這樣:

  # Acme Agent
  job_application_proof_request_json = json.dumps({
      'nonce': '1432422343242122312411212',
      'name': 'Job-Application',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {
              'name': 'first_name'
          },
          'attr2_referent': {
              'name': 'last_name'
          },
          'attr3_referent': {
              'name': 'degree',
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          },
          'attr4_referent': {
              'name': 'status',
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          },
          'attr5_referent': {
              'name': 'ssn',
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          },
          'attr6_referent': {
              'name': 'phone_number'
          }
      },
      'requested_predicates': {
          'predicate1_referent': {
              'name': 'average',
              'p_type': '>=',
              'p_value': 4,
              'restrictions': [{'cred_def_id': faber_transcript_cred_def_id}]
          }
      }
  })

請注意有些屬性是可以證實的,有些不可以。

Proof 請求要求 Credential 中的 SSN,學位和畢業狀態必須要由一個發行者和 schema_key 來進行正規地證明(asserted)。還需要注意的是,first_name,last_name和電話號碼不需要可證實。通過沒有標記這些屬性需要證實的方式,Acme 的 credential request 的意思是他們可以接受 Alice 的名字及電話號碼這些個人的 credential。

為了顯示 Alice 可以用來建立 Job-Application Proof 請求的 Proof 的 Credentials,Alice 呼叫了 anoncreds.prover_get_credentials_for_proof_req

 # Alice Agent
    creds_for_job_application_proof_request = json.loads(
        await anoncreds.prover_get_credentials_for_proof_req(alice_wallet, job_application_proof_request_json))

Alice 只有一項 credential 滿足 Job Application 的 proof 要求。

  # Alice Agent
  {
    'referent': 'Transcript Credential Referent',
    'attrs': {
        'first_name': 'Alice',
        'last_name': 'Garcia',
        'status': 'graduated',
        'degree': 'Bachelor of Science, Marketing',
        'ssn': '123-45-6789',
        'year': '2015',
        'average': '5'
    },
    'schema_id': job_certificate_schema_id,
    'cred_def_id': faber_transcript_cred_def_id,
    'rev_reg_id': None,
    'cred_rev_id': None
  }

現在 Alice 可以將這些屬性分為三個組:

  1. 屬性值將會被透漏的
  2. 屬性值將不會被透漏的
  3. 對於建立可證實的 proof 不需要的屬性

對於這個 Job-Application Proof Request,Alice 將屬性按照下邊的方式分組:

  # Alice Agent
    job_application_requested_creds_json = json.dumps({
        'self_attested_attributes': {
            'attr1_referent': 'Alice',
            'attr2_referent': 'Garcia',
            'attr6_referent': '123-45-6789'
        },
        'requested_attributes': {
            'attr3_referent': {'cred_id': cred_for_attr3['referent'], 'revealed': True},
            'attr4_referent': {'cred_id': cred_for_attr4['referent'], 'revealed': True},
            'attr5_referent': {'cred_id': cred_for_attr5['referent'], 'revealed': True},
        },
        'requested_predicates': {'predicate1_referent': {'cred_id': cred_for_predicate1['referent']}}
    })

另外,Alice 必須為每一個使用的 Credential 取得 Credential Schema 和對應的 Credential Definition,就像在建立 Credential 請求時候的步驟一樣。

現在 Alice 有了建立 Acme Job-Application 建立 Proof Request 的 Proof 的所有資訊。

# Alice Agent
  apply_job_proof_json = \
        await anoncreds.prover_create_proof(alice_wallet, job_application_proof_request_json, job_application_requested_creds_json,
                                            alice_master_secret_id, schemas_json, cred_defs_json, revoc_states_json)

Acme 公司 收到了這個 Proof 的時候,他們看到的應該是下邊的結構:

  # Acme Agent
  {
      'requested_proof': {
          'revealed_attrs': {
              'attr4_referent': {'sub_proof_index': 0, 'raw':'graduated', 'encoded':'2213454313412354'},
              'attr5_referent': ['sub_proof_index': 0, 'raw':'123-45-6789', 'encoded':'3124141231422543541'},
              'attr3_referent': ['sub_proof_index': 0, 'raw':'Bachelor of Science, Marketing', 'encoded':'12434523576212321'}
          },
          'self_attested_attrs': {
              'attr1_referent': 'Alice',
              'attr2_referent': 'Garcia',
              'attr6_referent': '123-45-6789'
          },
          'unrevealed_attrs': {},
          'predicates': {
              'predicate1_referent': {'sub_proof_index': 0}
          }
      },
      'proof' : [] # Validity Proof that Acme can check
      'identifiers' : [ # Identifiers of credentials were used for Proof building
          {
            'schema_id': job_certificate_schema_id,
            'cred_def_id': faber_transcript_cred_def_id,
            'rev_reg_id': None,
            'timestamp': None
          }
      }
  }

Acem 得到了所有所需的屬性。現在 Acem 想要校驗這個 Validity Proof。Acem 必須要獲得在 Proof 中提及的每個 identifier 的 Credential Schema 和相對應的 Credential Definition,就像 Alice 之前做的一樣。現在 Acme 有了驗證來自於 Alice 的 Job-Application Proof 的所有資訊。

# Acme Agent
 assert await anoncreds.verifier_verify_proof(job_application_proof_request_json, apply_job_proof_json,
                                              schemas_json, cred_defs_json, revoc_ref_defs_json, revoc_regs_json)

這裡我們會假設這個申請表被接受了並且 Alice 最終得到了這份工作。Acem 為 Alice 建立了一個新的 Credential Offer。

  # Acme Agent
  job_certificate_cred_offer_json = await anoncreds.issuer_create_credential_offer(acme_wallet, acme_job_certificate_cred_def_id)

當 Alice 檢視她和 Acme 的連線的時候,她能夠看到一個新的 Credential Offer 是可用的狀態。

申請貸款

現在 Alice 有了一份工作,她想要申請一份貸款。那個會要求提供僱傭證明。她能夠從由 Acme 公司提供的 Job-Certificate credential 得到這個證明。Alice 會使用一套熟悉的順序流程。

  1. 首先她會建立一個 Credential Request。
# Alice Agent
   (job_certificate_cred_request_json, job_certificate_cred_request_metadata_json) = \
       await anoncreds.prover_create_credential_req(alice_wallet, alice_acme_did, job_certificate_cred_offer_json,
                                                    acme_job_certificate_cred_def, alice_master_secret_id)
  1. Acme 會為 Alice 發行一個 Job-Certificate Credential。
 # Acme Agent
 alice_job_certificate_cred_values_json = json.dumps({
     "first_name": {"raw": "Alice", "encoded": "245712572474217942457235975012103335"},
     "last_name": {"raw": "Garcia", "encoded": "312643218496194691632153761283356127"},
     "employee_status": {"raw": "Permanent", "encoded": "2143135425425143112321314321"},
     "salary": {"raw": "2400", "encoded": "2400"},
     "experience": {"raw": "10", "encoded": "10"}
 })
 job_certificate_cred_json, _, _ = \
     await anoncreds.issuer_create_credential(acme_wallet, job_certificate_cred_offer_json,job_certificate_cred_request_json,
                                              alice_job_certificate_cred_values_json, None, None)

現在 Job-Certificate Credential 被髮行出來了,並且 Alice 現在也在她的財產中擁有了這個 Credential。Alice 將這個 Job-Certificate Credential 儲存在她的錢包中。

  # Alice Agent
  await anoncreds.prover_store_credential(alice_wallet, None, job_certificate_cred_request_json, job_certificate_cred_request_metadata_json,
                                          job_certificate_cred_request_json, acme_job_certificate_cred_def_json, None)

她可以在申請貸款的時候使用這個 credential 了,就像她在申請工作的時候使用她的成績單一樣。

但是這裡對於資料的共享還是有一點不足的 - 除了必要的一些資料,它可能會暴漏更多的資料。如果 Alice 需要提供的是僱傭證明,這個是可以使用匿名的 credential 的。匿名 credentials 可以在不暴漏真實的值的條件下來證明某些事實(比如,Alice 是全時的僱員,工資要高於 X,還有她的被僱傭時間,但是她的真實工資是保持隱藏的)。也可以建立一種混合的 proof,由 Faber 大學和 Acme Corp 提供,並且僅僅暴漏必須的資訊。

Alice 現在跟 Thrift 銀行建立了連線。

Alice 從 Thrift 銀行那裡得到了一個 Load-Application-Basic Proof 請求,像下邊這樣:

  # Thrift Agent
  apply_loan_proof_request_json = json.dumps({
      'nonce': '123432421212',
      'name': 'Loan-Application-Basic',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {
              'name': 'employee_status',
              'restrictions': [{'cred_def_id': acme_job_certificate_cred_def_id}]
          }
      },
      'requested_predicates': {
          'predicate1_referent': {
              'name': 'salary',
              'p_type': '>=',
              'p_value': 2000,
              'restrictions': [{'cred_def_id': acme_job_certificate_cred_def_id}]
          },
          'predicate2_referent': {
              'name': 'experience',
              'p_type': '>=',
              'p_value': 1,
              'restrictions': [{'cred_def_id': acme_job_certificate_cred_def_id}]
          }
      }
  })

對於這個 Loan-Application-Basic Proof Request,Alice 只有一項 credential 滿足這個 的 proof 的要求。

  # Alice Agent
  {
      'referent': 'Job-Certificate Credential Referent',
      'revoc_reg_seq_no': None,
      'schema_id': job_certificate_schema_id,
      'cred_def_id': acme_job_certificate_cred_def_id,
      'attrs': {
          'employee_status': 'Permanent',
          'last_name': 'Garcia',
          'experience': '10',
          'first_name': 'Alice',
           'salary': '2400'
      }
  }

對於這個 Loan-Application-Basic Proof Request,Alice 把這些屬性像下邊這樣進行了分類:

  # Alice Agent
  apply_loan_requested_creds_json = json.dumps({
      'self_attested_attributes': {},
      'requested_attributes': {
          'attr1_referent': {'cred_id': cred_for_attr1['referent'], 'revealed': True}
      },
      'requested_predicates': {
          'predicate1_referent': {'cred_id': cred_for_predicate1['referent']},
          'predicate2_referent': {'cred_id': cred_for_predicate2['referent']}
      }
  })

Alice 為這個 Loan-Application-Basic Proof Request 建立 Proof。

  # Alice Agent
  alice_apply_loan_proof_json = \
      await anoncreds.prover_create_proof(alice_wallet, authdecrypted_apply_loan_proof_request_json, apply_loan_requested_creds_json,
                                          alice_master_secret_id, schemas_json, cred_defs_json, revoc_states_json)

Alice 僅僅將這個 Loan-Application-Basic 證明發送給銀行。這個允許她在只需要證明一些基本資訊的時候,將最少的 PII(Personal Identifiable Information)分享出去。

Thrift 銀行 接收到這個證明後,他們會看到下邊的結構:

  # Thrift Agent
  {
      'requested_proof': {
          'revealed_attributess': {
              'attr1_referent': {'sub_proof_index': 0, 'raw':'Permanent', 'encoded':'2143135425425143112321314321'},
          },
          'self_attested_attrs': {},
          'unrevealed_attrs': {},
          'predicates': {
              'predicate1_referent': {'sub_proof_index': 0},
              'predicate2_referent': {'sub_proof_index': 0}
          }
      },
      'proof' : [] # Validity Proof that Thrift can check
      'identifiers' : [ # Identifiers of credentials were used for Proof building
          'schema_id': job_certificate_schema_id,
          'cred_def_id': acme_job_certificate_cred_def_id,
          'revoc_reg_seq_no': None,
          'timestamp': None
      ]
  }

Thrift 銀行 成功地驗證了來自於 Alice 的 Loan-Application-Basic Proof。

  # Thrift Agent
  assert await anoncreds.verifier_verify_proof(apply_loan_proof_request_json, alice_apply_loan_proof_json,
                                               schemas_json, cred_defs_json, revoc_defs_json, revoc_regs_json)

Thrift 銀行傳送了第二個證明請求(Proof Request),這裡 Alice 需要向銀行共享她的個人資訊。

  # Thrift Agent
  apply_loan_kyc_proof_request_json = json.dumps({
      'nonce': '123432421212',
      'name': 'Loan-Application-KYC',
      'version': '0.1',
      'requested_attributes': {
          'attr1_referent': {'name': 'first_name'},
          'attr2_referent': {'name': 'last_name'},
          'attr3_referent': {'name': 'ssn'}
      },
      'requested_predicates': {}
  })

Alice 現在滿足了兩條 Loan-Application-KYC Proof Request 的證明要求。

  # Alice Agent
  {
    'referent': 'Transcript Credential Referent',
    'schema_id': transcript_schema_id,
    'cred_def_id': faber_transcript_cred_def_id,
    'attrs': {
        'first_name': 'Alice',
        'last_name': 'Garcia',
        'status': 'graduated',
        'degree': 'Bachelor of Science, Marketing',
        'ssn': '123-45-6789',
        'year': '2015',
        'average': '5'
    },
    'rev_reg_id': None,
    'cred_rev_id': None
  },
  {
      'referent': 'Job-Certificate Credential Referent',
      'schema_key': job_certificate_schema_id,
      'cred_def_id': acme_job_certificate_cred_def_id,
      'attrs': {
          'employee_status': 'Permanent',
          'last_name': 'Garcia',
          'experience': '10',
          'first_name': 'Alice',
          'salary': '2400'
      },
      'rev_reg_id': None,
      'revoc_reg_seq_no': None
  }

對於 Loan-Application-KYC Proof Request,Alice 將它的屬性分為以下幾組:

  # Alice Agent
  apply_loan_kyc_requested_creds_json = json.dumps({
      'self_attested_attributes': {},
      'requested_attributes': {
          'attr1_referent': {'cred_id': cred_for_attr1['referent'], 'revealed': True},
          'attr2_referent': {'cred_id': cred_for_attr2['referent'], 'revealed': True},
          'attr3_referent': {'cred_id': cred_for_attr3['referent'], 'revealed': True}
      },
      'requested_predicates': {}
  })

Alice 為 Loan-Application-KYC Proof Request 建立了 Proof。

  # Alice Agent
  alice_apply_loan_kyc_proof_json = \
      await anoncreds.prover_create_proof(alice_wallet, apply_loan_kyc_proof_request_json, apply_loan_kyc_requested_creds_json,
                                          alice_master_secret_id, schemas_json, cred_defs_json, revoc_states_json)

Thrift 銀行 接收到這個證明後,他們會看到下邊的結構:

  # Thrift Agent
  {
      'requested_proof': {
          'revealed_attributes': {
              'attr1_referent': {'sub_proof_index': 0, 'raw':'123-45-6789', 'encoded':'3124141231422543541'},
              'attr1_referent': {'sub_proof_index': 1, 'raw':'Alice', 'encoded':'245712572474217942457235975012103335'},
              'attr1_referent': {'sub_proof_index': 1, 'raw':'Garcia', 'encoded':'312643218496194691632153761283356127'},
          },
          'self_attested_attrs': {},
          'unrevealed_attrs': {},
          'predicates': {}
      },
      'proof' : [] # Validity Proof that Thrift can check
      'identifiers' : [ # Identifiers of credentials were used for Proof building
          {
            'schema_id': transcript_schema_id,
            'cred_def_id': faber_transcript_cred_def_id,
            'rev_reg_id': None,
            'timestamp': None
          },
          {
            'schema_key': job_certificate_schema_id,
            'cred_def_id': acme_job_certificate_cred_def_id,
            'rev_reg_id': None,
            'timestamp': None
          }
      ]
  }

Thrift 銀行 成功地驗證了來自於 Alice 的 Loan-Application-KYC Proof。

  # Thrift Agent
  assert await anoncreds.verifier_verify_proof(apply_loan_kyc_proof_request_json, alice_apply_loan_kyc_proof_json,
                                               schemas_json, cred_defs_json, revoc_defs_json, revoc_regs_json)

Alice 的兩個證明都被成功地驗證通過了,她從 Thrift 銀行 那裡得到了貸款。

檢視程式碼

現在你有機會來檢視一下 Libindy 實現是如何來工作的了,或許你也想了解一下程式碼是如何實現的?如果是的話,請執行 Simulating Getting Started in the Jupiter。你可能需要登入 GitHub 來檢視這個連結。你也可以在這裡找到原始碼。