Hyperledger Indy:一个使用 Libindy 构建 Indy 客户端的开发指南

Hyperledger Indy

原文地址:https://github.com/hyperledger/indy-sdk/blob/master/doc/getting-started/getting-started.md

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 来查看这个链接。你也可以在这里找到源代码。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值