博客鏈接:https://hello2mao.github.io/2019/12/01/sidetree-protocol-overview/
1. 概述
1.1. Sidetree
Sidetree 是一個基於現有區塊鏈的第二層(L2)協議,專門用於去中心化身份(DID)的管理。
目前 DIF 中有兩個基於區塊鏈的、使用 Sidetree 協議的 DID 實現:
- ION:微軟開源 的 ION 項目,基於比特幣的、使用 Sidetree 協議的 DID 實現。
- Element:Transmute Industries 與 ConsenSys 合作的項目,基於以太坊的、使用 Sidetree 協議的 DID 實現。
1.2. DID
去中心化身份(Decentralized ID, DID)用來解決目前中心化身份系統的一系列問題,W3C 制定了一套 DID 的標準:W3C DID Spec,而DIF則基於此標準給出了 DID 的實現方案。
簡單來說,did 是類似如下的一個字符串,用戶使用此字符串來標識自己的身份。
DID 解決方案使用 DID 文檔、可驗證聲明、PKI 體系等來解決去中心化身份管理的五大核心挑戰:
- 表示:用來描述主體身份的可遷移表示
- 持久化:用來存儲、提取主體身份的機制,同時還需要保持其隱私
- 隱私:在去中心化賬本中保護主體身份的模型
- 斷言: 確定主體身份的特定語句
- 解析:解析、驗證特定主體身份的機制
2. Sidetree 協議
2.1. 協議概述
區塊鏈的 TPS 一般都比較低,例如比特幣的 TPS 大概爲 7~8,所以如果把 DID 相關數據上鍊,那麼會遇到嚴重的性能問題。所以,出現了 Sidetree 協議,它是一個區塊鏈 L2 層的協議,核心思想是把 DID 的批量操作打包進一個區塊鏈交易中,從而顯著的提高 DID Operation 的數目。
2.2. 工作原理
- Sidetree 節點互相連接構成一個 L2 層的 P2P 網絡,每個 Sidetree 節點都對外暴露 Restful API 來處理 DID 的 CURD 操作。
- Sidetree 節點儘可能多的收集 DID 操作,然後把這批操作打包,並創建一個 L1 鏈上交易並在交易中嵌入該操作批次的哈希。
- 批操作的源數據會推送到內容尋址存儲(IPFS)上。當其他節點獲知嵌入 Sidetree 操作的底層鏈上交易後,這些節點將向原始節點或其他 IPFS 節點請求該批次數據。
- 當一個節點收到某個批次後,它會將元數據固定到本地,然後 Sidetree 核心邏輯模塊解壓批次數據來 解析並驗證其中的每個操作。
需要注意的是:目標鏈的區塊/交易體系是 Sidetree 協議唯一需要的共識機制,不需要額外的 區塊鏈、側鏈或諮詢權威單元來讓網絡中的 DID 達成正確的 PKI 狀態。
2.3. Sidetree 協議的 DID 操作
2.3.1. DID OP
DID 操作不外乎 CURD。
2.3.2. Batch File 和 Anchor File
如下所示,把批操作的源數據推送到內容尋址存儲(IPFS)上時,會存在兩種文件:Batch File
和 Anchor File
:
Batch File:
{
"operations": [
"Encoded operation",
"Encoded operation",
...
]
}
Anchor File:
{
"batchFileHash": "Encoded multihash of the batch file.",
"didUniqueSuffixes": ["Unique suffix of DID of 1st operation", "Unique suffix of DID of 2nd operation", "..."],
"merkleRoot": "Encoded multihash of the root of the Merkle tree constructed from the operations included in the batch file."
}
可以看到,Batch File
是原始數據,Anchor File
是 Batch File
的元數據,而 L1 鏈上存儲的是 Anchor File
的 hash。
爲什麼要這樣設計呢?
這其實是爲了後續實現 Sidetree 輕節點預留的,因爲進行 DID 解析(例如 DID URL Dereferrence)的時候,如果只是需要一些元數據,那麼只需下載Anchor File
即可,而不需要把較大的Batch File
下載下來。
2.4. Sidetree REST API
實現 Sidetree 協議的節點需要提供 REST API,且所有的請求都需要使用 JWS 簽名。
提供的接口列表如下,詳細的可參考:sidetree-rest-api
- DID and DID Document Creation
- DID Document resolution
- Updating a DID Document
- DID Deletion
- DID Recovery
- Fetch the current service versions (optional)
3. Sidetree 協議 的 Node.js 實現
3.1. 整體架構
Sidetree Node.js 實現的整體架構如下圖所示,可以認爲有三部分組成:
- Sidetree 節點:實現 Sidetree 協議的節點
- 區塊鏈及其適配器
- CAS(Content Address Storage)存儲,目前是 IPFS
3.2. 分層設計
爲了解決 Sidetree 協議的後向兼容性,採取分層實現。
- Orchestration Layer (編排層):與協議版本無關的通用模塊放在此層中實現。
- Protocol Version Specific Layer (版本適配層):與協議版本相關的模塊放在此層中實現。
3.3. REST API
3.3.1. Blockchain REST API
- Get latest blockchain time
- Get blockchain time by hash
- Fetch Sidetree transactions
- Get first valid Sidetree transaction
- Write a Sidetree transaction
- Fetch normalized transaction fee for proof-of-fee calculation
- Fetch the current service version
3.3.2. CAS REST API
- Read content
- Write content
- Fetch the current service version
4. ION: 基於比特幣的、使用 Sidetree 協議的 DID 實現
4.1. 使用 ION(macOS)
參考官方教程:ION Installation Guide
(1) 啓動比特幣客戶端,並同步測試網
$ cat bitcoin.conf
testnet=1
server=1
rpcuser=hello2mao
rpcpassword=123
$ bitcoind -datadir=/xxx/btc
(2) 啓動 MongoDB
$ cat /usr/local/etc/mongod.conf
systemLog:
destination: file
path: /usr/local/var/log/mongodb/mongo.log
logAppend: true
storage:
dbPath: /usr/local/var/mongodb
net:
bindIp: 127.0.0.1
$ mongod --config /usr/local/etc/mongod.conf
(3) 下載 ION 並 build
git clone https://github.com/decentralized-identity/ion
cd ion
npm run build
(4) 啓動 Sidetree 的區塊鏈適配層微服務,ION 是比特幣的實現
npm run bitcoin
(5) 啓動 Sidetree 的 CAS,ION 是 IFPS 網絡的適配層微服務
npm run ipfs
(6)啓動 Sidetree 核心服務
npm run core
(7)創建 ION DID
使用 ION 的 js sdk 創建 ION DID,如下:
var didAuth = require("@decentralized-identity/did-auth-jose");
var http = require("http");
async function createIONDid() {
// gen key
const kid = "#key-1";
const jwkPriv = await didAuth.EcPrivateKey.generatePrivateKey(kid);
const jwkPub = jwkPriv.getPublicKey();
jwkPub.defaultSignAlgorithm = "ES256K";
// load JWK into an EcPrivateKey object
const privateKey = didAuth.EcPrivateKey.wrapJwk(jwkPriv.kid, jwkPriv);
// construct the JWS payload
const body = {
"@context": "https://w3id.org/did/v1",
publicKey: [
{
id: jwkPub.kid,
type: "Secp256k1VerificationKey2018",
publicKeyJwk: jwkPub
}
],
service: [
{
id: "IdentityHub",
type: "IdentityHub",
serviceEndpoint: {
"@context": "schema.identity.foundation/hub",
"@type": "UserServiceEndpoint",
instance: ["did:test:hub.id"]
}
}
]
};
// Construct the JWS header
const header = {
alg: jwkPub.defaultSignAlgorithm,
kid: jwkPub.kid,
operation: "create",
proofOfWork: "{}"
};
// Sign the JWS
const cryptoFactory = new didAuth.CryptoFactory([
new didAuth.Secp256k1CryptoSuite()
]);
const jwsToken = new didAuth.JwsToken(body, cryptoFactory);
const signedBody = await jwsToken.signAsFlattenedJson(privateKey, { header });
// Print out the resulting JWS to the console in JSON format
console.log("Request: \n" + JSON.stringify(signedBody));
console.log("\n");
const data = JSON.stringify(signedBody);
var options = {
host: "127.0.0.1",
port: 3000,
path: "/",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": data.length
}
};
var req = http.request(options, function(res) {
// console.log("STATUS: " + res.statusCode);
// console.log("HEADERS: " + JSON.stringify(res.headers));
res.setEncoding("utf8");
res.on("data", function(chunk) {
console.log("Response: \n" + chunk);
});
});
req.on("error", function(e) {
console.log("problem with request: " + e.message);
});
// write data to request body
req.write(data);
req.end;
}
createIONDid();
Output:
Request:
{
"header": {
"alg": "ES256K",
"kid": "#key-1",
"operation": "create",
"proofOfWork": "{}"
},
"payload": "eyJAY29udGV4dCI6Imh0dHBzOi8vdzNpZC5vcmcvZGlkL3YxIiwicHVibGljS2V5IjpbeyJpZCI6IiNrZXktMSIsInR5cGUiOiJTZWNwMjU2azFWZXJpZmljYXRpb25LZXkyMDE4IiwicHVibGljS2V5SndrIjp7Imt0eSI6IkVDIiwia2lkIjoiI2tleS0xIiwiY3J2IjoiUC0yNTZLIiwieCI6Ikp6UTNiQWZmUzc2Y3R3dEJ4S0NBbnhMcXcyckRlaEd3eU9POGwta1dNclkiLCJ5IjoiSGlLb0xwbWdEVXhHSkhQdHJseHkzd2JPREZhWHA5OHhXUndleGRnTWlFVSIsImRlZmF1bHRFbmNyeXB0aW9uQWxnb3JpdGhtIjoibm9uZSIsImRlZmF1bHRTaWduQWxnb3JpdGhtIjoiRVMyNTZLIn19XSwic2VydmljZSI6W3siaWQiOiJJZGVudGl0eUh1YiIsInR5cGUiOiJJZGVudGl0eUh1YiIsInNlcnZpY2VFbmRwb2ludCI6eyJAY29udGV4dCI6InNjaGVtYS5pZGVudGl0eS5mb3VuZGF0aW9uL2h1YiIsIkB0eXBlIjoiVXNlclNlcnZpY2VFbmRwb2ludCIsImluc3RhbmNlIjpbImRpZDp0ZXN0Omh1Yi5pZCJdfX1dfQ",
"signature": "MEYCIQDIrTPcCV35zQRojk8KtlMAsbJKsbnMt8uEOD0XUspOUwIhAIbeS1r9dPU6cGvyNnWbChGR36HRG3VILr78M39xeG1H"
}
Response:
{
"@context": "https://w3id.org/did/v1",
"publicKey": [
{
"id": "#key-1",
"type": "Secp256k1VerificationKey2018",
"publicKeyJwk": {
"kty": "EC",
"kid": "#key-1",
"crv": "P-256K",
"x": "JzQ3bAffS76ctwtBxKCAnxLqw2rDehGwyOO8l-kWMrY",
"y": "HiKoLpmgDUxGJHPtrlxy3wbODFaXp98xWRwexdgMiEU",
"defaultEncryptionAlgorithm": "none",
"defaultSignAlgorithm": "ES256K"
}
}
],
"service": [
{
"id": "IdentityHub",
"type": "IdentityHub",
"serviceEndpoint": {
"@context": "schema.identity.foundation/hub",
"@type": "UserServiceEndpoint",
"instance": [
"did:test:hub.id"
]
}
}
],
"id": "did:ion:test:EiBNbUbOyzSmE66Akhc-6fYoo_A6QPF15VHSRNFLIJgUsw"
}
則新建的 DID 爲:did:ion:test:EiBNbUbOyzSmE66Akhc-6fYoo_A6QPF15VHSRNFLIJgUsw
(8)查詢 ION DID
使用 ION 的 js sdk 查詢 ION DID,如下:
const http = require("http");
const options = {
hostname: "127.0.0.1",
port: 3000,
path: "/did:ion:test:EiBNbUbOyzSmE66Akhc-6fYoo_A6QPF15VHSRNFLIJgUsw",
method: "GET"
};
const req = http.request(options, res => {
// console.log("statusCode:", res.statusCode);
// console.log("headers:", res.headers);
res.on("data", d => {
console.log("Response: \n" + d);
});
});
req.on("error", e => {
console.error(e);
});
req.end();
Output:
Response:
{
"document": {
"@context": "https://w3id.org/did/v1",
"publicKey": [
{
"id": "#key-1",
"type": "Secp256k1VerificationKey2018",
"publicKeyJwk": {
"kty": "EC",
"kid": "#key-1",
"crv": "P-256K",
"x": "JzQ3bAffS76ctwtBxKCAnxLqw2rDehGwyOO8l-kWMrY",
"y": "HiKoLpmgDUxGJHPtrlxy3wbODFaXp98xWRwexdgMiEU",
"defaultEncryptionAlgorithm": "none",
"defaultSignAlgorithm": "ES256K"
}
}
],
"service": [
{
"id": "IdentityHub",
"type": "IdentityHub",
"serviceEndpoint": {
"@context": "schema.identity.foundation/hub",
"@type": "UserServiceEndpoint",
"instance": [
"did:test:hub.id"
]
}
}
],
"id": "did:ion:test:EiBNbUbOyzSmE66Akhc-6fYoo_A6QPF15VHSRNFLIJgUsw"
},
"resolverMetadata": {
"driverId": "did:ion:test",
"driver": "HttpDriver",
"retrieved": "2019-10-08T07:54:21.793Z",
"duration": "49.3152ms"
}
}