UNPKG

@arcblock/did

Version:

Javascript lib to work with ArcBlock DID

177 lines (175 loc) 5.82 kB
import { toBits, toBytes, toStrictHex } from "./util.mjs"; import { Hasher, types } from "@ocap/mcrypto"; import { BN, isHex, numberToHex, stripHexPrefix, toAddress } from "@ocap/util"; import isEqual from "lodash/isEqual.js"; import pick from "lodash/pick.js"; import upperFirst from "lodash/upperFirst.js"; //#region src/type.ts const mapping = { pk: "key", address: "encoding" }; const DID_TYPE_ARCBLOCK = { role: types.RoleType.ROLE_ACCOUNT, pk: types.KeyType.ED25519, hash: types.HashType.SHA3, address: types.EncodingType.BASE58 }; const DID_TYPE_PASSKEY = { role: types.RoleType.ROLE_PASSKEY, pk: types.KeyType.PASSKEY, hash: types.HashType.SHA2, address: types.EncodingType.BASE58 }; const DID_TYPE_ETHEREUM = { role: types.RoleType.ROLE_ACCOUNT, pk: types.KeyType.ETHEREUM, hash: types.HashType.KECCAK, address: types.EncodingType.BASE16 }; const isEthereumType = (type) => { const props = [ "pk", "hash", "address" ]; return isEqual(pick(type, props), pick(DID_TYPE_ETHEREUM, props)); }; /** * Checks if the given string is an address * * @method isEthereumDid * @param {String} address the given HEX address * @return {Boolean} */ const isEthereumDid = (did) => { const address = toAddress(did); if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) return false; if (/^(0x|0X)?[0-9a-f]{40}$/.test(address) || /^(0x|0X)?[0-9A-F]{40}$/.test(address)) return true; return checkAddressChecksum(address); }; /** * Checks if the given string is a checksummed address * * @method checkAddressChecksum * @param {String} address the given HEX address * @return {Boolean} */ const checkAddressChecksum = (address) => { const origin = address.replace(/^0x/i, ""); const addressHash = Hasher.Keccak.hash256(origin.toLowerCase()).replace(/^0x/i, ""); for (let i = 0; i < 40; i++) if (Number.parseInt(addressHash[i], 16) > 7 && origin[i].toUpperCase() !== origin[i] || Number.parseInt(addressHash[i], 16) <= 7 && origin[i].toLowerCase() !== origin[i]) return false; return true; }; /** * Converts to a checksum address * * @method toChecksumAddress * @param {String} address the given HEX address * @return {String} */ const toChecksumAddress = (address) => { if (typeof address === "undefined") return ""; if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) throw new Error(`Given address "${address}" is not a valid Ethereum address.`); const lower = address.toLowerCase().replace(/^0x/i, ""); const addressHash = Hasher.Keccak.hash256(lower).replace(/^0x/i, ""); let checksumAddress = "0x"; for (let i = 0; i < lower.length; i++) if (Number.parseInt(addressHash[i], 16) > 7) checksumAddress += lower[i].toUpperCase(); else checksumAddress += lower[i]; return checksumAddress; }; /** * Create an wallet type object that be used as param to create a new wallet */ function DidType(type = {}) { let input = {}; if (["default", "arcblock"].includes(type)) input = DID_TYPE_ARCBLOCK; else if (["eth", "ethereum"].includes(type)) input = DID_TYPE_ETHEREUM; else if (["passkey"].includes(type)) input = DID_TYPE_PASSKEY; else input = { ...DID_TYPE_ARCBLOCK, ...type }; const { role, pk, hash, address } = input; Object.keys(input).forEach((x) => { const key = upperFirst(`${mapping[x] || x}Type`); if (Object.values(types[key]).includes(input[x]) === false) throw new Error(`Unsupported ${x} type: ${input[x]}`); }); const output = { role, pk, hash, address }; if (isEthereumType(output) === false) { if ([ types.RoleType.ROLE_NODE, types.RoleType.ROLE_VALIDATOR, types.RoleType.ROLE_TETHER, types.RoleType.ROLE_SWAP ].includes(output.role)) output.hash = types.HashType.SHA2; } return output; } DidType.toJSON = (type) => Object.keys(type).reduce((acc, x) => { const key = upperFirst(`${mapping[x] || x}Type`); acc[x] = Object.keys(types[key])[Object.values(types[key]).indexOf(type[x])]; return acc; }, {}); DidType.fromJSON = (json) => Object.keys(json).reduce((acc, x) => { const key = upperFirst(`${mapping[x] || x}Type`); const typeStr = Object.keys(types[key]); acc[x] = Object.values(types[key])[typeStr.indexOf(json[x])]; return acc; }, {}); /** * Convert type info object to hex string * * @public * @static * @param {object} type - wallet type, {@see @arcblock/did#DidType} * @returns string */ const fromTypeInfo = (type) => { const info = DidType(type); const infoBits = `${toBits(info.role, 6)}${toBits(info.pk, 5)}${toBits(info.hash, 5)}`; return toStrictHex(stripHexPrefix(numberToHex(Number.parseInt(infoBits, 2))), 4); }; /** * Get type info from did */ const toTypeInfo = (did) => { let type = {}; try { if (isEthereumDid(did)) type = DID_TYPE_ETHEREUM; else { const typeBytes = toBytes(did).slice(0, 2); const typeBits = toBits(new BN(toStrictHex(Buffer.from(Uint8Array.from(typeBytes)).toString("hex")), 16), 16); const roleBits = typeBits.slice(0, 6); const keyBits = typeBits.slice(6, 11); const hashBits = typeBits.slice(11, 16); type = { role: Number.parseInt(roleBits, 2), pk: Number.parseInt(keyBits, 2), hash: Number.parseInt(hashBits, 2), address: isHex(toAddress(did)) ? types.EncodingType.BASE16 : types.EncodingType.BASE58 }; } Object.keys(type).forEach((x) => { if (Object.values(types[`${upperFirst(mapping[x] || x)}Type`]).includes(type[x]) === false) delete type[x]; }); return type; } catch { return type; } }; const toTypeInfoStr = (did) => { const type = toTypeInfo(did); return Object.keys(type).reduce((acc, x) => { const enums = types[`${upperFirst(mapping[x] || x)}Type`]; acc[x] = Object.keys(enums).find((d) => enums[d] === type[x]); return acc; }, {}); }; //#endregion export { DID_TYPE_ARCBLOCK, DID_TYPE_ETHEREUM, DID_TYPE_PASSKEY, DidType, fromTypeInfo, isEthereumDid, isEthereumType, toChecksumAddress, toTypeInfo, toTypeInfoStr };