去中心化数字身份DID简介——三、用户属性的选择性披露
在上一篇文章中,我们以最简单的生成DID,颁发VC,验证VP流程介绍了DID的用法,但是在实际生活中,我们并不总是希望直接将整个证件VC亮给验证者看,比如我们去住酒店时,需要登记姓名、身份证号信息,但是如果我们直接把身份证给前台人员的话,前台人员就可以看到我们的民族、住址等信息,对于我们普通人来说,也许觉得没什么,那要是明星、公众人物去住酒店,那么可能前台人员就可能出于各方面的原因偷偷把住址信息记下了或者泄露到网上,给证照本人的生活带来各种麻烦。那么我们有什么办法呢?用户属性的选择性披露能够降低风险。
我们以小明从公安机关获得身份证VC,然后在住酒店时,只出示姓名、照片和身份证号,不对外暴露民族和住址(因为身份证编号里面已经有生日了,所以我们就忽略掉出生日期属性)为例,说明用户属性的选择性披露的处理过程。
0x0.准备知识1:默克尔树
关于Merkle Tree默克尔树的介绍,网上已经很多了,只要讲解比特币基本原理的应该都会讲到默克尔树。使用默克尔树的目的是为了能够将一个区块中的所有交易形成一个短小的指纹(默克尔根,哈希值),并将这个指纹放到区块头,任何对交易的篡改都会导致指纹变化。而我们使用默克尔树而不是直接将区块中所有的交易直接算哈希的原因是因为我们希望能够进行快速的简单支付验证(SPV)。
我们以4个交易组成的默克尔树为例,我们要验证Data2是否被包含在区块中,只需要给出:
- 要验证的原始数据:Data2
- 要验证的数据所在的位置索引:1 (索引以0开始,所以Data2的索引是1)
- 验证路径:[Hash1,Hash34]
- 默克尔根:MerkleRoot
通过这4个参数,我们就能确认Data2是否被包含在这棵树中了。
0x1.准备知识2:基于种子的无限序列
基于前面提到的默克尔树和默克尔验证,我们可以将用户的属性作为Data部分计算默克尔树,比如我们要对身份证上的属性构建默克尔树:
基于上面的默克尔树,我们可以只暴露生日,而不暴露其他字段,然后给出验证路径,从而证明生日数据的真实性。但是这里有个潜在的隐私泄露问题,就是我们会暴露Hash1,而攻击者是可以穷举所有民族数据,算出每个民族的Hash然后进行比对,从而得出我并不想暴露的”民族“属性。那么怎么办呢?最简单的办法就是我们为每个字段都加点”盐“。
用户在生成默克尔树之前,需要先生成一个随机的种子,并将这个种子数据保存下来,然后基于这个种子生成N个序列(N取决与我们默克尔树的叶子节点数),因为我们的种子是随机生成的,所以我们可以认为这个序列也是随机的。最简单的办法就是用哈希函数,不断的对上一个数据进行Hash,从而得到下一个数据。以下是我的序列生成函数:
func GenerateSequence256(seed []byte, count int) [][]byte { result := [][]byte{} current := seed for i := 0; i < count; i++ { current = getHash(current) result = append(result, current) } return result } func getHash(input []byte) []byte { h := sha256.New() h.Write(input) return h.Sum(nil) }
有了这个无限长的随机序列,那么我们就可以将每一个默克尔树叶子节点加盐了,如下图所示:
现在我们Hash值都是加了盐后计算的结果,所以不可能再被碰撞出原始数据。
0x2.生成VC
基于上面的两个知识点,我们在准备VC数据时,除了给出证件中的每个属性外,还需要给出:随机种子seed,默克尔根,发证机关对默克尔根的签名。我们以身份证为例,那么公安机关颁发给我们的身份证VC如下所示:
{ // VC内容所遵循的JSON-LD标准 "@context": [ "https://www.w3.org/2018/credentials/v1", "https://studyzyexamples.com/identity/v1" ], // 本VC的唯一标识,也就是证书ID "id": "vc511112200001010015", // VC内容的格式 "type": ["VerifiableCredential", "Identity"], // 本VC的发行人 "issuer": "did:公安部门ID", // 本VC的发行时间 "issuanceDate": "2010-07-01T19:73:24Z", // VC声明的具体内容 "credentialSubject": { // 被声明的人的DID "id": "did:cid:511112200001010015", // 声明内容:姓名、性别、生日、民族、住址等 "name":"小明", "gender":"男", "birthdate":"2000-01-01", "nation":"汉", "address":"A省B市C区D街道xxx号", //接下来是种子数、默克尔根、公安的签名 "seed":"23523865082340324", "merkleRoot":"ea59a369466be42d1a4783f09ae0721a5a157d6dba9c4b053d407b5a4b9af145", "rootSignature":"3066022051757c2de7032a0c887c3fcef02ca3812fede7ca748254771b9513d8e266", "signer":"did:公安部门ID#keys-1" }, // 对本VC的证明 "proof": { "creator": "did:公安部门ID#keys-1", "type": "Secp256k1", "signatureValue": "3044022051757c2de7032a0c887c3fcef02ca3812fede7ca748254771b9513d8e2bb" } }
这里我们需要注意的是对默克尔根的签名并不是在proof字段里面,而是在credentialSubject里面,proof里面的签名是对整个VC数据的签名,而rootSignature是公安机关对构造的默克尔树的根哈希进行的签名。
0x3.生成VP
接下来假如小明要去参加一个生日当天免费送礼品的活动,活动方要验证小明的出生日期,于是小明可根据上一步骤的VC,生成对应的VP,其中只暴露生日字段,其他身份属性不暴露,示例如下:
{ "@context": [ "https://www.w3.org/2018/credentials/v1", "https://studyzyexamples.com/identity/v1" ], "type": "VerifiablePresentation", // 本VP包含的VC的内容 "verifiableCredential": [{ "@context": [ "https://www.w3.org/2018/credentials/v1", "https://studyzyexamples.com/identity/v1" ], "id": "vc511112200001010015", "type": ["VerifiableCredential", "Identity"], "issuer": "did:公安部门ID", "issuanceDate": "2010-07-01T19:73:24Z", "credentialSubject": { "id": "did:cid:511112200001010015", //以下是要选择性披露的内容 "birthdate":"2000-01-01", //以下是验证披露字段有效性的数据 //数据在默克尔树中的索引 "dataIndex":2, //本数据加盐的值 "salt":"6b264354ed367ced527a86d38f75f9c3888bd3939f548cc48d93af435890b84a", //默克尔验证路径 "merklesibling":"34b64151443c3124620bf4ff69a05e97d580f0878b374b8343c6a5c3d8223435 9d2b5b35ccb5bf18747c1f5dc05771c68ce613e6eb0c5f5ef77cec8ba3e9da67 bb82c63d4e21525125bf66a6724fbb4dcbded26aae2baa2633235dc12730016e", //默克尔根哈希 "merkleRoot":"ea59a369466be42d1a4783f09ae0721a5a157d6dba9c4b053d407b5a4b9af145", //公安机关对默克尔根的签名 "rootSignature":"3066022051757c2de7032a0c887c3fcef02ca3812fede7ca748254771b9513d8e266", //用的公安机关哪个Key进行的签名 "signer":"did:公安部门ID#keys-1" }, }], // Holder小明对本VP的签名信息 "proof": { "type": "Secp256k1", "created": "2010-07-02T21:19:10Z", "proofPurpose": "authentication", "verificationMethod": "did:cid:511112200001010015#keys-1", // challenge和domain是为了防止重放攻击而设计的 "challenge": "1f44d55f-f161-4938-a659-f8026467f126", "domain": "4jt78h47fh47", "jws": "eyJhbGciOiJSUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..kTCYt5 XsITJX1CxPCT8yAV-TVIw5WEuts01mq-pQy7UJiN5mgREEMGlv50aqzpqh4Qq_PbChOMqs LfRoPsnsgxD-WUcX16dUOqV0G_zS245-kronKb78cPktb3rk-BuQy72IFLN25DYuNzVBAh 4vGHSrQyHUGlcTwLtjPAnKb78" } }
这里我们给出了选择披露的字段“birthdate”,然后给出了要进行默克尔验证的几个必不可少的字段:
- "birthdate":"2000-01-01”,要披露的原始数据内容。
- dataIndex,披露字段在构架默克尔树时在叶子节的索引,因为我们这里按:姓名、性别、生日、民族、住址排序,所以生日的索引值是2.
- salt,对birthdate这个字段加的盐,这个数据小明可以根据自己VC中的seed和dataIndex计算得到。
- merkleSibling是进行默克尔验证所需的路径,以前面提到的4个Data为例,如果我们要算Data2的验证路径,那么就是Hash1,Hash34。
- merkleRoot默克尔根,这个就不再累述。
- rootSignature和signer是用来验证默克尔根的合法性的。merkleRoot、rootSignature、signer验证通过,则说明这个默克尔根是公安部认证过的。
0x4.验证VP
商家在收到用户提交的VP后,需要进行逐步的验证,主要包括以下步骤:
1.根据小明的DID从区块链中获取小明的DID文档,从中获得公钥,验证VP签名真实有效。
2.根据VC中的issuer,从区块链中获得公安机关的DID文档,从文档中获得公钥,另外也验证该DID是一个可信的DID。
3.根据公安部门的公钥,验证默克尔根的签名是否正确。
4.对披露字段、Dataindex、Salt、MerkleSibling、MerkleRoot等进行默克尔验证,保证披露字段是公安机关认证过的。
5.以上所有步骤验证通过,显示可信的披露内容:"birthdate":"2000-01-01”。
商家验证完成了小明的出生日期,但是并没有获得除了出生日期之外的其他身份信息,从而实现了选择性披露。
0x5.小结
以上我们讲解了在用户身份中具有多个属性时,用户只选择性的暴露其中某个属性,而且基于默克尔证明,给出了可信的证明字段,任何用户在收到VP后都可以进行合法性验证。同时我们基于随机种子生成了一个随机序列,并将随机序列作为盐添加到每个字段中,从而防止了潜在的默克尔验证时暴露的哈希值导致其他身份属性被碰撞的事情发生。
下一篇,我们更进一步的讲解使用零知识的方式证明小明大于18岁,但是却不暴露小明具体的生日。