vscode 更改語言設定

以MacOS為例,在vscode介面同時按下:⇧⌘P 三個鍵後,輸入”Configure Display language”,然後選擇喜歡的語言。

Decentralized ID(DID)

1. 什麼是中心化身份

中心化身份系統的身份數據被中央權威機構所把持,相關的授權、認證行為也都交給中心化機構進行,用戶無法直接控制自己的身份。除此之外,用戶在不同中心化系統中可能擁有不同身份,每到新的系統就要註冊一個新的帳號,例如:在蝦皮、Pchome上,我們會註冊不同的名字,可能用不同的信箱、不同的密碼。為了解決前述問題,各個網站聯合起來推出了聯盟身份,使身份具有可移植性,也發展第三方登錄,例如:使用已註冊的Facebook、Google來登錄。

上述來看,中心化身份主要問題有兩點:一、 用戶並不是真的擁有這個身份,二、不同系統間身份無法互通。

2. 什麼是DID

DID全名為Decentralized identifiers,作為去中心化世界的為匿名性(Pseudo -Anonymity)身份識別,是一種較新的標識符(Identifier)。

每個DID都被私鑰(private key)所保護,擁有私鑰才能證明自己是這個去中心化識別的所有者,在與其他服務相連時,通常會用公鑰加密來建立安全的通訊。

一但使用者和DID進行完綁定,結合Verifiable Credentials (VC)技術加上不同系統設計,使用者可以透過例如QR code的方式,向特定服務證明自己的去中心化身份。DID加上VC,使用者擁有身份自主權(Self-Sovereign identity),也提高資料可攜性(Data Portability),在遇到KYC這種驗證身份的情況,較傳統方式快速,不需要重複繁雜的手續、文件檢查,也有效減少身份詐騙(identity fraud)。  

3. Decentralized Identity(DID)現狀

雖然在區塊鏈領域中熱度遠遠低於Defi、側鏈,他還是有它的價值在,下面有一些關於DID的標準與相關項目:

  1. 標準
    1. W3C的DID標準
    2. DIF(Decentralized Identity Foundation)的DID Auth
  2. 項目
    1. 微軟的MicrosoftDID
    2. 位於Hyperledger的Sovrin
    3. 位於以太坊的uPort
    4. 用於交易的Evernym
    5. 使生物識別的多因素身份認證、移動身份平台Civic
    6. 移動身份平台、保護隱私ShoCard  

4. DID結構

DID 結構

DID

Decentralized Identifier,一種全球性的獨一無二的標識符,不需要任何中心化權威機構,通常使用密碼學的方式建立。

DID是一種由三段字串組成的URI:

  1. scheme identifier
  2. the identifier for the DID method
  3. the DID method-specific identifier

以以太坊常見的DID為例:可能是did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9

method的不同,會影響建構DID method-specific identifier與解析DID Document的方式,上例是以以太坊帳號的壓縮公鑰作為DID method-specific identifier。

DID Method

  • 必須清楚定義如何產生method-specific-id
  • 任何由DID Method產生的DID必須要全球獨一無二
  • 減少因為method name產生的conflict,DID method 的規格應該在DID Specification Registries 註冊
  • DID method規格必須定義好如何產生DID、DID resolver如何產生DID document、DID controller如何更新DID document
  • 目前已註冊正在研發中的DID Method規格
    • 專案使用以太坊基金會旗下uPort團隊的 ethr method

DID document

包含與這個DID有關的資訊,通常是指驗證這個DID的方法(verification methods),例如密碼學的公私鑰,以及與這個DID Subject交互的services。DID document的內容被更新,更新方法依不同的methods而定。

DID Document Properties :

Verification Method properties

範例:

{
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://identity.foundation/EcdsaSecp256k1RecoverySignature2020/lds-ecdsa-secp256k1-recovery2020-0.0.jsonld"
  ],
  "id": "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9",
  "verificationMethod": [
    {
      "id": "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9#controller",
      "type": "EcdsaSecp256k1RecoveryMethod2020",
      "controller": "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9",
      "blockchainAccountId": "0xb7d0249d66683da475642930eA36C46a12554616@eip155:1"
    },
    {
      "id": "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9#controllerKey",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9",
      "publicKeyHex": "037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9"
    }
  ],
  "authentication": [
    "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9#controller",
    "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9#controllerKey"
  ],
  "assertionMethod": [
    "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9#controller",
    "did:ethr:0x037edc7768938d46e18fa08681bb15967dbec0a8a6be8e4fac3279bbe1d03008c9#controllerKey"
  ]
}

DID subjects

被該DID識別的entity,可以是人、組織、任何東西,甚至可以是一個概念。

DID subject同時也可以是DID controller。

DID controllers

DID subject的控制者,可以是人、組織、某自動化的軟體,有能力依照DID method定義好的方法對DID document做更動,這種能力通常使用一組公私鑰來證明。

DID可以有一或多個controller。

DID Methods

和DID Method不同,是一種針對DID document建立、解析、更新、停用的特殊機制,會因DID method而異。

Verifiable data registries

用來記錄DID與解析DID document所需資訊的任何系統都可以叫做verifiable data registry,包含分散式帳本、去中心化儲存,任何形式可信任的資料庫。

DID resolvers & DID resolution

DID resolver是一種輸入DID後會分析並建構出DID document+額外metadata的元件,這樣的過程被稱作DID resolution,通常是到verifiable data registry來“讀取”需要的資料完成resolution,會依照DID method的差異有不同解析方法。

verification method

在Spec裡面的原文:A set of parameters that can be used together with a process to independently verify a proof,例如可以將公鑰當作verification method,使其他人可以用來驗證關聯的私鑰簽名過的數位簽章(proof)

  • Structure
    "verificationMethod": [{
      "id": ...,
      "type": ...,
      "controller": ...,
      "publicKeyJwk": ...
    }, {
      "id": ...,
      "type": ...,
      "controller": ...,
      "publicKeyMultibase": ...
    }]
    
  • Structure中的 publicKeyJwkpublicKeyMultibase 被稱作 Verification Material
  • type,例如JsonWebKey2020

verification relationship

在Spec裡面的原文:An expression of the relationship between the DID subject and a verification method. Different verification relationships enable the associated verification methods to be used for different purposes. 主要分為以下幾種:

  • Authentication
  • Assertion
  • KeyAgreement
  • Capability Invocation
  • Capability Delegation

DID delegate

DID controller可以授權給別人使用驗證機制(verification method),但代理人沒有controller的允許沒辦法進行添加或更改。

services

通過一或多個service endpoints(網路地址例如HTTP URL)和DID subject或相關實體進行交互的方法,例如:discovery services、agent services、social networking services、file storage services、verifiable credential repository services  

5. 以太坊上的DID與相似項目

ERC 1056 - Ethereum Lightweight Identity

  • 官方提案網址
  • 提案動機:
    • ERC725建立身份所需的費用過高
  • 提案重點:
    • 使用區塊鏈上的資源建立去中心化身份,每個以太坊帳號其實都是一個identity

      → 創建身份免費

      → 支援任何擁有key-pair的錢包

    • 可在離線環境中進行,鏈上鏈下均可簽名
    • 可在不更改 primary identifier 的情況下輪換密鑰
    • 身份可透過更新秘鑰移轉給其他地址
    • 可以將DID委託給第三方進行操作、設置權限,並由第三方支付gas fee
  • 合約:EthereumDIDRegistry

ERC 1484 - Digital Identity Aggregator

  • 官方提案網址
  • 基於之前所有數位身分提案(ERCs/EIPs 7257357801056 等)所衍生出的新型數字聚合協議,也可視為一種整合。
  • 提案動機:
    • 隨著數位身分相關提案越來越多,因為各自重點不同,開發人員與用戶有時需要進行繁瑣重複的作業
    • 過去的提案通常把身份概念跟以太坊其中一塊(如:智能合約、簽名驗證)聯繫起來,這個提案避免這些方法,選擇在以太坊網路與個人身份間添加了一層協議層(protocal layer),該協議曾兼容與整合其他協議標準

其餘性質類似但非DID的提案還有 EIP-137: Ethereum Domain Name Service - SpecificationEIP-4361: Sign-In with Ethereum  

參考資料:

  1. ENS names are Decentralized Identifiers (DIDs)
  2. 一文讀懂去中心化身份(DID):通往Web3的護照
  3. How Does Identity Work Today?
  4. W3C
  5. Alice Attempts to Abuse a Verifiable Credential
  6. DID and VC: Untangling Decentralized Identifiers and Verifiable Credentials for the Web of Trust

util.insprct

有時使用console.log印出JSON檔時,會顯示很惱人的:

verificationMethod: [ [Object], [Object] ]

這時候可以用

const util = require('util')
console.log(util.inspect(myObject, false, null, true))

來解決

cannot load such file -- nokogiri/nokogiri

可能添加了一些新東西到Gemfile,接著執行bundle install,安裝好後想執行bundle exec jekyll serve卻發生錯誤: require': cannot load such file -- nokogiri/nokogiri (LoadError)

參考許多網路解法後,有效的是這一篇

  1. gem uninstall -aIx指令卸載全部的gems
  2. gem install bundler安裝新的bundler
  3. bundle install安裝全部的gems

問題解決!

使用Hardhat與Infura部署合約

小試身手

1.Retrieve the Current Block Number

curl https://mainnet.infura.io/v3/YOUR_PROJECT_ID \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params": [],"id":1}'

回傳了以十六進制表示的block number:{"jsonrpc":"2.0","id":1,"result":"0xdf9bca"}

2.Check the Ether Balance For a Given Contract

curl https://rinkeby.infura.io/v3/YOUR_PROJECT_ID \
-X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_getBalance","params": ["0xBf4eD7b27F1d666546E30D74d50d173d20bca754", "latest"],"id":1}'

回傳{"jsonrpc":"2.0","id":1,"result":"0x159fa21f4e8b7e715de0"} ,result中的字串代表合約的balance,單位是wei,一個Ether約等於10¹⁸ wei


建立交易看看吧

1.建立Ethereum帳號(可使用例如metamask) 2.建資料夾立然後進入

mkdir sendTransaction
cd sendTransaction

3.安裝需要的packages

npm install web3
npm install dotenv --save

💡 `dotenv` package用來讓使用者建立.env檔案、安全地存放本地環境變數。

4.建立.env檔案,內容:

ETHEREUM_NETWORK = "rinkeby"
INFURA_PROJECT_ID = "<Project-ID>"
SIGNER_PRIVATE_KEY = "<Private-Key>"
  • <Project-ID> 在infura dashboard→setting 內可以找到
  • <Private-Key> 以太坊帳號私鑰,導出metamask私鑰可看這篇
  • ETHEREUM_NETWORK 因自己在哪個網路而異,不一定是rinkeby

5.建立send.js檔案,內容:

const Web3 = require("web3");

async function main() {
  // Configuring the connection to an Ethereum node
  const network = process.env.ETHEREUM_NETWORK;
  const web3 = new Web3(
    new Web3.providers.HttpProvider(
      `https://${network}.infura.io/v3/${process.env.INFURA_PROJECT_ID}`
    )
  );
  // Creating a signing account from a private key
  const signer = web3.eth.accounts.privateKeyToAccount(
    process.env.SIGNER_PRIVATE_KEY
  );
  web3.eth.accounts.wallet.add(signer);
  // Creating the transaction object
  const tx = {
    from: signer.address,
    to: "0xAED01C776d98303eE080D25A21f0a42D94a86D9c",
    value: web3.utils.toWei("0.001"),
  };
  // Assigning the right amount of gas
  tx.gas = await web3.eth.estimateGas(tx);

  // Sending the transaction to the network
  const receipt = await web3.eth
    .sendTransaction(tx)
    .once("transactionHash", (txhash) => {
      console.log(`Mining transaction ...`);
      console.log(`https://${network}.etherscan.io/tx/${txhash}`);
    });
  // The transaction is now on chain!
  console.log(`Mined in block ${receipt.blockNumber}`);
}

require("dotenv").config();
main();

6.執行

node send.js

7.可到輸出結果第二行的網址查看細節


結合Hardhat部署合約到測試鏈上吧


💡 使用npx hardhat指令初始化時就選擇 Create an advanced sample project that uses TypeScript 選項的話,下面將js改為ts的操作可跳過

前置作業

1.安裝Hardhat

npm install --save-dev hardhat

2.建立資料夾並進入

mkdir infura_project
cd infura_project

3.建立Hardhat project,選擇第一個 Create a basic sample project

npx hardhat

4.MacOS的環境下,可以使用 tree -L 1 指令印出第一層資料夾樹狀結構,沒有安裝過tree的話,可透過 brew install tree 安裝


撰寫合約與測試

1.將contract資料夾底下的Greeter.sol改名為Demo.sol,替換程式碼內容為:

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";

contract Demo is Ownable{
    uint256 private _value = 0;
    event ValueChanged(uint256 value);

    function store(uint256 value) public onlyOwner {
        _value = value;
        emit ValueChanged(value);
    }

    function retrieve() public view returns (uint256) {
        return _value;
    }
}

2.合約需要用到openzepplin的Ownable.sol,使用指令安裝@openzeppelin/contracts

npm install @openzeppelin/contracts

3.編譯合約看看

npx hardhat compile

會列印出 Compiled 3 Solidity files successfully,資料夾目錄底下多出artifactscache兩個資料夾

4.將test資料夾底下的sample-test.js 改名為Demo.test.ts,內容為:

import { expect } from 'chai'
import { ethers } from 'hardhat'

// Start test block
describe('Demo', function () {
  before(async function () {
    this.Demo = await ethers.getContractFactory('Demo')
  })

  beforeEach(async function () {
    this.demo = await this.Demo.deploy()
    await this.demo.deployed()
  })

  // Test case
  it('retrieve returns a value previously stored', async function () {
    // Store a value
    await this.demo.store(42)

    // Test if the returned value is the same one
    // Note that we need to use strings to compare the 256 bit integers
    expect((await this.demo.retrieve()).toString()).to.equal('42')
  })
})

這時候會報錯,這是因為我們接下來會用typescript進行開發,需要更改package.json內容:

{
  "name": "hardhat-project",
  "devDependencies": {
    "@nomiclabs/hardhat-ethers": "^2.0.5",
    "@nomiclabs/hardhat-waffle": "^2.0.3",
    "@types/chai": "^4.3.1",
    "@types/mocha": "^9.1.1",
    "@types/node": "^17.0.27",
    "chai": "^4.3.6",
    "dotenv": "^16.0.0",
    "ethereum-waffle": "^3.4.4",
    "ethers": "^5.6.4",
    "hardhat": "^2.9.3",
    "mocha": "^9.2.2",
    "solc": "^0.8.13",
    "ts-node": "^10.7.0",
    "typescript": "^4.6.3",
    "web3": "^1.7.3"
  },
  "dependencies": {
    "@openzeppelin/contracts": "^4.5.0",
    "@typechain/hardhat": "^6.0.0"
  }
}

再執行一次 npm install ,回到Demo.test.ts就看不到紅字了!

5.更改hardhat.config.js.ts,檔頭require("@nomiclabs/hardhat-waffle"); 改為

import { task } from "hardhat/config";
import "@nomiclabs/hardhat-waffle";
import "@nomiclabs/hardhat-ethers";

6.添加tsconfig.json檔案

{
  "compilerOptions": {
    "target": "ES2017",
    "module": "commonjs",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "rootDirs": ["./src", "./scripts", "./test"],
    "esModuleInterop": true
  },
  "exclude": ["dist", "node_modules"],
  "include": ["./test", "./src", "./scripts"],
  "files": ["./hardhat.config.ts"]
}

7.使用 npx hardhat test 指令進行測試,順利的話,最後會出現 1 passing


撰寫腳本進行部署

1.將scripts資料夾下的sample-scripts.js更改為deploy.ts,內容為:

import { ethers } from "hardhat";

async function main() {
  const [deployer] = await ethers.getSigners(); //get the account to deploy the contract

  console.log("Deploying contracts with the account:", deployer.address); 

  const Demo = await ethers.getContractFactory("Demo"); // Getting the Contract
  const demo = await Demo.deploy(); //deploying the contract

  await demo.deployed(); // waiting for the contract to be deployed

  console.log("Demo deployed to:", demo.address); // Returning the contract address on the rinkeby
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  }); // Calling the function to deploy the contract

2.在hardhat.config.ts中配置網路,添加在solidity: "0.8.4"後面

export default {
  solidity: "0.8.4",
  defaultNetwork: "rinkeby",
  networks: {
    hardhat: {
    },
    rinkeby: {
      url: "https://rinkeby.infura.io/v3/YOUR_PROJECT_ID", //Infura url with projectId
      accounts: ["YOUR_ACCOUNT_PRIVATE_KEY"] // add the account that will deploy the contract (pay gas fee)
    }
  },

3.執行指令 TS_NODE_FILES=true ts-node scripts/deploy.ts ,如果是使用測試鏈,可能需要等待比較久的時間,成功後應該會看到結果:

上面是部署合約、支付gas fee的帳號,下面是合約地址,把合約地址複製起來,去Etherscan看看:

4.在scrips資料夾底下建立叫做storeAndRetrieve.ts的新檔案,測試剛剛部署好的合約的兩個function,store()retrieve()

import { ethers } from "hardhat";

async function main () {
    const accounts = await ethers.provider.listAccounts();

    const address = 'YOUR_CONTRACT_ADDRESS';
    const Demo = await ethers.getContractFactory('Demo');
    const demo = await Demo.attach(address);

    // Call the retrieve() function of the deployed Box contract
    let value = await demo.retrieve();
    console.log('demo value is', value.toString());
}
  
main()
  .then(() => process.exit(0))
  .catch(error => {
  console.error(error);
  process.exit(1);
  });

5.使用 TS_NODE_FILES=true ts-node scripts/storeAndRetrieve.ts 指令執行,會呼叫合約中的retrieve(),印出_value原始的值0。

監聽合約事件

1.在主目錄底下建立src資料夾,裡面建立一個做eventListener.ts的檔案

import { ethers } from 'hardhat'

async function listenEvent () {
  const address = 'YOUR_CONTRACT_ADDRESS'
  const Demo = await ethers.getContractFactory('Demo')
  const demo = await Demo.attach(address)

  demo.on('ValueChanged', async (event: any) => {
    console.log(event)
  })
}

listenEvent()

2.修改storeAndRetrieve.ts,在let value = await demo.retrieve(); 前面加上三行,代表呼叫合約中的store(uint value),並變更_value的值成100

// Send a transaction to store() a new value
    let storeValue = await demo.store(100);
    await storeValue.wait();

3.執行指令 TS_NODE_FILES=true ts-node src/eventListener.ts 持續監聽合約內的事件 ValueChanged

4.在新的terminal分頁執行 TS_NODE_FILES=true ts-node scripts/storeAndRetrieve.ts

5.過了一陣子,回到剛剛執行eventListener.ts的地方,因為呼叫store function來存入100,觸發了事件ValueChanged,合約監聽器會印出BigNumber { value: “100” }

6.可以回到Etherscan做確認,應該會看到兩筆交易,兩筆事件

資料來源: