Sidetree協議

博客鏈接: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. 工作原理

  1. Sidetree 節點互相連接構成一個 L2 層的 P2P 網絡,每個 Sidetree 節點都對外暴露 Restful API 來處理 DID 的 CURD 操作。
  2. Sidetree 節點儘可能多的收集 DID 操作,然後把這批操作打包,並創建一個 L1 鏈上交易並在交易中嵌入該操作批次的哈希。
  3. 批操作的源數據會推送到內容尋址存儲(IPFS)上。當其他節點獲知嵌入 Sidetree 操作的底層鏈上交易後,這些節點將向原始節點或其他 IPFS 節點請求該批次數據。
  4. 當一個節點收到某個批次後,它會將元數據固定到本地,然後 Sidetree 核心邏輯模塊解壓批次數據來 解析並驗證其中的每個操作。

需要注意的是:目標鏈的區塊/交易體系是 Sidetree 協議唯一需要的共識機制,不需要額外的 區塊鏈、側鏈或諮詢權威單元來讓網絡中的 DID 達成正確的 PKI 狀態。

2.3. Sidetree 協議的 DID 操作

2.3.1. DID OP

DID 操作不外乎 CURD。

2.3.2. Batch File 和 Anchor File

如下所示,把批操作的源數據推送到內容尋址存儲(IPFS)上時,會存在兩種文件:Batch FileAnchor 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 FileBatch 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"
  }
}

5. 參考

發佈了68 篇原創文章 · 獲贊 40 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章