Skip to content

Latest commit

 

History

History
251 lines (184 loc) · 11.6 KB

index.md

File metadata and controls

251 lines (184 loc) · 11.6 KB

Getting Started

Construct a New Identity

Create Ethr-DID from scratch

Creating an Ethr-DID is analogous to creating an Ethereum account, which is an address on the Ethereum blockchain controlled by a key pair. Your Ethr-DID will be your key pair.

We provide a convenient method to easily create one EthrDID.createKeyPair() which returns an object containing an Ethereum address and private key.

import { EthrDID } from 'ethr-did'

const keypair = EthrDID.createKeyPair()
const ethrDid = new EthrDID({...keypair})
// this creates a DID like:
// did:ethr:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b

This represents a DID that is based on a secp256k1 key pair (ethereum key pair) and that is anchored on the Ethereum mainnet. As you may have noticed, no transaction was necessary to anchor the DID. This is by design and it is possible because by default all EthrDIDs resolve to their own public key or ethereum address. Later on we will see how to update a DID document, in which case a transaction will be needed.

To create a DID that is anchored on a different network, you must specify a chainNameOrId property.

let chainNameOrId = 'goerli' // you can use the network name for the most popular [test] networks.
const ethrDidOnGoerliNamed = new EthrDID({...keypair, chainNameOrId})
// did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b

chainNameOrId = 5 // goerli chain ID
const ethrDidOnGoerliChainId = new EthrDID({...keypair, chainNameOrId})
// did:ethr:0x5:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b

Use Existing Web3 Provider

If you use a built-in web3 provider like metamask you can use one of your metamask addresses as your identifier.

const provider = new Web3Provider((window as any).ethereum);
const chainNameOrId = (await provider.getNetwork()).chainId
const accounts = await provider.listAccounts()

const ethrDid = new EthrDID({identifier: accounts[0], provider, chainNameOrId})

Unfortunately, web3 providers are not directly able to sign data in a way that is compliant with the JWT ES256K or the (unregistered) ES256K-R algorithms. This is a requirement for exchanging verifiable off-chain data, so you will need to add a key pair as a delegate or as an attribute to be able to sign JWTs.

You can quickly add one like this:

const { kp, txHash } = await ethrDid.createSigningDelegate() // Adds a signing delegate valid for 1 day

This will allow you to use the web3 based EthrDID to sign JWTs using the (unregistered) ES256K-R algorithm. To support other algorithms, please check out the section on adding attributes below.

Note

You don't have to use one of the accounts of the web3 provider, it's ok to use key-pair accounts even in a web3 context. A Provider that links to the corresponding chainNameOrId is needed if you wish to make updates to the DID document. You can also use a rpcUrl instead, and a JsonRPCProvider will be created internally. See the section on managing keys below. If you are not using a Provider that can sign on behalf of the owner address but still want to update the DID document, you need to supply a corresponding txSigner or privateKey to the constructor.

Resolving the DID document

To inspect the corresponding DID document for the DID you just created you can use the ethr-did-resolver library. This document is used by verifiers to check that the signatures issued by your DID match the keys listed for it.

import { Resolver } from 'did-resolver'
import { getResolver } from 'ethr-did-resolver'

const rpcUrl = "https://goerli.infura.io/v3/<get a project ID from infura.io>";
const didResolver = new Resolver(getResolver({ rpcUrl, name: "goerli" }));

const didDocument = (await didResolver.resolve(ethrDidOnGoerliNamed.did)).didDocument

/**
 {
  "@context": [
    "https://www.w3.org/ns/did/v1",
    "https://w3id.org/security/suites/secp256k1recovery-2020/v2"
  ],
  "id": "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b",
  "verificationMethod": [
    {
      "id": "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b#controller",
      "type": "EcdsaSecp256k1RecoveryMethod2020",
      "controller": "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b",
      "blockchainAccountId": "eip155:5:0x80155C25E363Ee9e1BbBCC08cD5Df7CD249A98C4"
    },
    {
      "id": "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b#controllerKey",
      "type": "EcdsaSecp256k1VerificationKey2019",
      "controller": "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b",
      "publicKeyHex": "0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b"
    }
  ],
  "authentication": [
    "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b#controller",
    "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b#controllerKey"
  ],
  "assertionMethod": [
    "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b#controller",
    "did:ethr:goerli:0x02ac49094591d32a4e2f93f3368da2d7d827e987ce6cdb3bd3b8a3390fde8fc33b#controllerKey"
  ]
}
 */

Exchange verifiable data

Signing JWT

A JWT is a JSON object that is signed so it can be verified as being created by a given DID. A JWT looks like this: eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6dXBvcnQ6Mm5RdGlRRzZDZ20xR1lUQmFhS0Fncjc2dVk3aVNleFVrcVgiLCJpYXQiOjE0ODUzMjExMzMsInJlcXVlc3RlZCI6WyJuYW1lIiwicGhvbmUiXX0.1hyeUGRBb-cgvjD5KKbpVJBF4TfDjYxrI8SWRJ-GyrJrNLAxt4MutKMFQyF1k_YkxbVozGJ_4XmgZqNaW4OvCw

If an EthrDID is initialized with a privateKey (as it is when creating it from a key pair) then it has the ability to sign JWT.

const helloJWT = await ethrDid.signJWT({hello: 'world'})

If you inspect the resulting JWT (you can do that on https://jwt.io or by calling did-jwt#decodeJWT()) , you will notice that the iss property of the payload is set to the DID that you created earlier.

Verifying a JWT

An EthrDID can also verify JWTs that have DIDs as issuers by leveraging the functionality of the did-jwt library.

You will need to use the didResolver you just created.

try {
  const { payload, issuer } = await ethrDid.verifyJWT(jwt, didResolver);
  // `issuer` contains the DID of the signing identity
  console.log(issuer)
} catch(e) {
  console.error('unable to verify JWT: ', e)
}

Manage Keys

The Ethr-DID supports general key management that can be used to change ownership of keys, delegate signing rights temporarily to another account, and update key and service information in its DID document.

The Concept of Identity Ownership

By default, in ERC1056 an ethereum address (or public key) is owned by itself. This means that creating a DID using EthrDID is free, as it doesn't involve the sending of transactions.

An owner is an address that is able to make and publish changes to the DID. As this is a very important function, you could change the ownership to use a smart contract based address implementing recovery or multi-sig at some point in the future. Smart contracts are not able to sign, so you would also need to add a key pair based address as a signing delegate.

Most web3 providers also don't allow users to sign data that is compatible with JWT standards. This means that you would have to add a separate delegate key that you can use to sign JWTs on your behalf.

All the following functions assume that the passed in web3 provider can sign Ethereum transactions on behalf of the owner address, or that you are using a txSigner to represent the owner address.

Changing an Owner

You can change the owner of an Ethr-DID. This is useful in particular if you are changing a provider and want to continue to use the same identifier.

This creates an Ethereum transaction, which will also broadcast a DIDOwnerChanged event. Make sure that the current account owner has sufficient gas to be able to update it.

await ethrDid.changeOwner(newOwner)

Changing the owner to the null address means deactivating it (and implicitly revoking all existing keys and services).

Adding a Delegate Signer

You can temporarily add a delegate signer to your DID. This is an address that can sign JWTs on your behalf. By adding an expiresIn value, it will automatically expire after a certain time. By default, it will expire after one day.

You can add different delegate types. The two types currently supported by DID-JWT are:

  • veriKey Which adds a EcdsaSecp256k1RecoveryMethod2020 to the assertionMethod section of the DID document (Default for signing general purpose JWTs using ES256K-R)
  • sigAuth Which adds a EcdsaSecp256k1RecoveryMethod2020 to the assertionMethod and authentication sections of the DID document.

This is useful if you want to give a dApp the permission to sign on your behalf. This creates an Ethereum transaction, so your current owner account needs sufficient gas to be able to update it.

await ethrDid.addDelegate(web3.eth.accounts[3])

// Override defaults to expire in 100 days and have ability to authenticate.
await ethrDid.addDelegate(web3.eth.accounts[3], {expiresIn: 8640000, delegateType: 'sigAuth'})

There also exists a convenience function that creates a new delegate key pair, configures a signer with it and finally calls the above addDelegate() function.

const {kp, txHash} = await ethrDid.createSigningDelegate('sigAuth', 360)

The kp object contains an address and privateKey attribute. Unless the key is just added temporarily, store it somewhere safe.

Delegate addresses can be queried both on-chain ( see validDelegate()) and off-chain through the DID document, but they can only represent ethereum addresses, which limits the types of proofs they can produce. (ES256K-R is not a registered algorithm for JWT). For this reason, it is often better to use attributes to add different types of keys with different capabilities.

Set Public Attributes

You can set various public attributes to your DID using setAttribute(key, value, expiresIn). These cannot be queried within smart contracts, but they let you publish information to your DID document such as public encryption keys.

The following attribute key formats are currently support:

  • did/pub/(Secp256k1|Rsa|Ed25519)/(veriKey|sigAuth)/(hex|base64|base58) for adding a public key
  • did/svc/[ServiceName] for adding a service

By adding an expiresIn value, it will automatically expire after a certain time. By default, it expires after one day.

await ethrDid.setAttribute('did/pub/Secp256k1/sigAuth/hex', '0x034cc8162c28eb201a4b538d6915d08889296a36df34ca76ab2401e804f31cae7a', 31104000)
await ethrDid.setAttribute('did/pub/Ed25519/veriKey/base64', Buffer.from('Arl8MN52fwhM4wgBaO4pMFO6M7I11xFqMmPSnxRQk2tx', 'base64'), 31104000)
await ethrDid.setAttribute('did/svc/HubService', 'https://hubs.uport.me', 10)

The did:ethr spec has extensive information on the various attributes you can use.