在上一篇文章中,我們以最簡單的生成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歲,但是卻不暴露小明具體的生日。