UNPKG

@lacchain/did

Version:

The LACChain DID Method NodeJS Implementation

375 lines (354 loc) 9.47 kB
import bs58 from "bs58"; import * as ethers from "ethers"; import DIDDocument from "../document.js"; import { bytes32toString, keyAlgorithms } from "../utils.js"; import { computeAddress, getAddress, keccak256 } from "ethers/lib/utils.js"; import { LAC1_DID_AKA_CHANGED_EVENT_NAME, LAC1_DID_ATTRIBUTE_CHANGED_EVENT_NAME, LAC1_DID_DELEGATE_CHANGED_EVENT_NAME, LAC1_SIG_AUTH_BYTES32_DELEGATE_TYPE_NAME, LAC1_VERI_KEY_BYTES32_DELEGATE_TYPE_NAME, } from "./constants.js"; const { BigNumber } = ethers; export function processIdentifierForVmOrService(prefix, valueInHex) { const valueArrayBuffer = Buffer.from(valueInHex.replace("0x", ""), "hex"); const prefixBuffer = Buffer.from(prefix, "utf-8"); const finalBuffer = Buffer.concat([prefixBuffer, valueArrayBuffer]); const digest = keccak256(finalBuffer); const bufferDigest = Buffer.from(digest.replace("0x", ""), "hex"); const base58Identifier = bs58.encode(bufferDigest); return base58Identifier; } export function getVerificationMethodForAttribute( did, algo, encoding, value, controller, chainId ) { const verificationMethodId = processIdentifierForVmOrService( controller, value ); return getVerificationMethod( did, algo, encoding, value, controller, verificationMethodId, chainId ); } export function getVerificationMethodForOnchainDelegate( did, algo, encoding, value, controller, chainId ) { const verificationMethodId = processIdentifierForVmOrService(did, value); return getVerificationMethod( did, algo, encoding, value, controller, verificationMethodId, chainId ); } export function getVerificationMethod( did, algo, encoding, value, controller, verificationMethodId, chainId ) { const verificationMethod = { id: `${did}#${verificationMethodId}`, type: `${keyAlgorithms[algo]}`, controller, }; switch (encoding) { case null: case undefined: case "hex": verificationMethod.publicKeyHex = value.slice(2); break; case "blockchain": verificationMethod.blockchainAccountId = computeCAPIP10(chainId, value); break; case "base64": verificationMethod.publicKeyBase64 = Buffer.from( value.slice(2), "hex" ).toString("base64"); break; case "base58": verificationMethod.publicKeyBase58 = bs58.encode( Buffer.from(value.slice(2), "hex") ); break; case "pem": verificationMethod.publicKeyPem = Buffer.from( value.slice(2), "hex" ).toString(); break; case "json": verificationMethod.publicKeyJwk = JSON.parse( Buffer.from(value.slice(2), "hex").toString() ); break; } return verificationMethod; } export function processValidVerificationMethodFromAttributeEvent( did, event, verificationMethods, services, relationships, value, key, chainId ) { const name = bytes32toString(event.args.name); const match = name.match( /(vm|auth|asse|keya|dele|invo|svc)\/(.+)?\/(\w+)?\/(\w+)?$/ ); if (!match) return; const type = match[1]; const controller = match[2]; const algo = match[3]; const encoding = match[4]; if (encoding == "blockchain") { return; // from now on, omitting blockchain accounts from attributes, since it may add unnecessary complexity. // To add a blockchain account id just add it as a delegate } switch (type) { case "vm": verificationMethods[key] = getVerificationMethodForAttribute( did, algo, encoding, value, controller, chainId ); break; case "auth": case "asse": case "keya": case "dele": case "invo": if (!algo && !encoding) { relationships[type][key] = Buffer.from( value.slice(2), "hex" ).toString(); return; } verificationMethods[key] = getVerificationMethodForAttribute( did, algo, encoding, value, controller, chainId ); relationships[type][key] = verificationMethods[key].id; break; case "svc": services[key] = { id: `${did}#${processIdentifierForVmOrService(type, value)}`, type: algo, serviceEndpoint: Buffer.from( event.args.value.slice(2), "hex" ).toString(), }; break; } } export function handleVerificationMethodRemoval( relationships, verificationMethods, services, key ) { delete relationships.auth[key]; delete relationships.asse[key]; delete relationships.keya[key]; delete relationships.dele[key]; delete relationships.invo[key]; delete verificationMethods[key]; delete services[key]; } export function processAKAEventsOnAddingAnElement(id, alsoKnownAs) { const idx = alsoKnownAs.indexOf(id); if (idx < 0) { alsoKnownAs.push(id); } } export function computeCAPIP10(intChainId, address) { const parsedAddress = getAddress(address); return `eip155:${intChainId}:${parsedAddress}`; } export function processValidVerificationMethodFromOnchainDelegateEvent( did, event, verificationMethods, chainId, key, relationships ) { const delegateType = event.args.delegateType; if ( !( delegateType == LAC1_SIG_AUTH_BYTES32_DELEGATE_TYPE_NAME || delegateType == LAC1_VERI_KEY_BYTES32_DELEGATE_TYPE_NAME ) ) { return; } const type = delegateType == LAC1_SIG_AUTH_BYTES32_DELEGATE_TYPE_NAME ? "auth" : "asse"; const delegate = event.args.delegate; const algo = "esecp256k1rm"; const encoding = "blockchain"; const vm = getVerificationMethodForOnchainDelegate( did, algo, encoding, delegate, did, chainId ); verificationMethods[key] = vm; relationships[type][key] = verificationMethods[key].id; // TODO: analyze this } function computeInternalKey(event) { let key; if (event.name == LAC1_DID_ATTRIBUTE_CHANGED_EVENT_NAME) { key = `${event.name}-${event.args.name}-${event.args.value}`; } else if (event.name == LAC1_DID_DELEGATE_CHANGED_EVENT_NAME) { const key = `${event.name}-${event.args.delegateType}-${event.args.value}`; } return key; } export function processAKAEventsOnRemovingElement(id, alsoKnownAs) { const idx = alsoKnownAs.indexOf(id); if (idx > -1) { alsoKnownAs.splice(idx, 1); } } export function wrapDidDocument( did, chainId, caip10ControllerFormat, controller, history, mode ) { const now = BigNumber.from(Math.floor(new Date().getTime() / 1000)); const valueInHex = caip10ControllerFormat.split(":")[2]; const defaultIdVerificationMethod = processIdentifierForVmOrService( did, valueInHex ); const defaultVerificationMethod = { id: `${did}#${defaultIdVerificationMethod}`, type: "EcdsaSecp256k1RecoveryMethod2020", controller: `${did}`, blockchainAccountId: caip10ControllerFormat, }; const relationships = { auth: {}, asse: {}, keya: {}, dele: {}, invo: {}, }; const verificationMethods = {}; const services = {}; const alsoKnownAs = []; for (const event of history) { const validTo = event.args.validTo; const key = computeInternalKey(event); const value = event.args.value; if (validTo && validTo.gte(now)) { if (event.name == LAC1_DID_ATTRIBUTE_CHANGED_EVENT_NAME) { processValidVerificationMethodFromAttributeEvent( did, event, verificationMethods, services, relationships, value, key, chainId ); } else if (event.name == LAC1_DID_AKA_CHANGED_EVENT_NAME) { const id = event.args.akaId; processAKAEventsOnAddingAnElement(id, alsoKnownAs); } else if (event.name == LAC1_DID_DELEGATE_CHANGED_EVENT_NAME) { processValidVerificationMethodFromOnchainDelegateEvent( did, event, verificationMethods, chainId, key, relationships ); } } else { if ( event.name == LAC1_DID_ATTRIBUTE_CHANGED_EVENT_NAME || event.name == LAC1_DID_DELEGATE_CHANGED_EVENT_NAME ) { handleVerificationMethodRemoval( relationships, verificationMethods, services, key ); } else if (event.name == LAC1_DID_AKA_CHANGED_EVENT_NAME) { const id = event.args.akaId; processAKAEventsOnRemovingElement(id, alsoKnownAs); } } } const doc = { "@context": [ "https://w3id.org/did/v1", "https://w3id.org/security/suites/jws-2020/v1", ], id: did, alsoKnownAs, controller, verificationMethod: [defaultVerificationMethod].concat( Object.values(verificationMethods) ), authentication: [] .concat([defaultVerificationMethod.id]) .concat(Object.values(relationships.auth)), assertionMethod: [] .concat([defaultVerificationMethod.id]) .concat(Object.values(relationships.asse)), keyAgreement: [].concat(Object.values(relationships.keya)), capabilityInvocation: [].concat(Object.values(relationships.invo)), capabilityDelegation: [].concat(Object.values(relationships.dele)), }; if (Object.values(services).length > 0) { doc.service = Object.values(services); } if (alsoKnownAs.length == 0) delete doc.alsoKnownAs; return new DIDDocument(doc, mode); }