@arcblock/did
Version:
Javascript lib to work with ArcBlock DID
244 lines (243 loc) • 8.83 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.toChecksumAddress = exports.isEthereumDid = exports.isEthereumType = exports.toTypeInfoStr = exports.toTypeInfo = exports.fromTypeInfo = exports.DID_TYPE_PASSKEY = exports.DID_TYPE_ETHEREUM = exports.DID_TYPE_ARCBLOCK = void 0;
exports.DidType = DidType;
/* eslint-disable @typescript-eslint/no-use-before-define */
/* eslint-disable @typescript-eslint/ban-ts-comment */
const util_1 = require("@ocap/util");
const upperFirst_1 = __importDefault(require("lodash/upperFirst"));
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const pick_1 = __importDefault(require("lodash/pick"));
const mcrypto_1 = require("@ocap/mcrypto");
const util_2 = require("./util");
const mapping = {
pk: 'key',
address: 'encoding',
};
const DID_TYPE_ARCBLOCK = {
role: mcrypto_1.types.RoleType.ROLE_ACCOUNT,
pk: mcrypto_1.types.KeyType.ED25519,
hash: mcrypto_1.types.HashType.SHA3,
address: mcrypto_1.types.EncodingType.BASE58,
};
exports.DID_TYPE_ARCBLOCK = DID_TYPE_ARCBLOCK;
const DID_TYPE_PASSKEY = {
role: mcrypto_1.types.RoleType.ROLE_PASSKEY,
pk: mcrypto_1.types.KeyType.PASSKEY,
hash: mcrypto_1.types.HashType.SHA2,
address: mcrypto_1.types.EncodingType.BASE58,
};
exports.DID_TYPE_PASSKEY = DID_TYPE_PASSKEY;
const DID_TYPE_ETHEREUM = {
role: mcrypto_1.types.RoleType.ROLE_ACCOUNT,
pk: mcrypto_1.types.KeyType.ETHEREUM,
hash: mcrypto_1.types.HashType.KECCAK,
address: mcrypto_1.types.EncodingType.BASE16,
};
exports.DID_TYPE_ETHEREUM = DID_TYPE_ETHEREUM;
const isEthereumType = (type) => {
const props = ['pk', 'hash', 'address'];
return (0, isEqual_1.default)((0, pick_1.default)(type, props), (0, pick_1.default)(DID_TYPE_ETHEREUM, props));
};
exports.isEthereumType = isEthereumType;
/**
* Checks if the given string is an address
*
* @method isEthereumDid
* @param {String} address the given HEX address
* @return {Boolean}
*/
const isEthereumDid = (did) => {
const address = (0, util_1.toAddress)(did);
// check if it has the basic requirements of an address
if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
return false;
}
// If it's ALL lowercase or ALL upppercase
if (/^(0x|0X)?[0-9a-f]{40}$/.test(address) || /^(0x|0X)?[0-9A-F]{40}$/.test(address)) {
return true;
}
// Otherwise check each case
return checkAddressChecksum(address);
};
exports.isEthereumDid = isEthereumDid;
/**
* Checks if the given string is a checksummed address
*
* @method checkAddressChecksum
* @param {String} address the given HEX address
* @return {Boolean}
*/
const checkAddressChecksum = (address) => {
// Check each case
const origin = address.replace(/^0x/i, '');
const addressHash = mcrypto_1.Hasher.Keccak.hash256(origin.toLowerCase()).replace(/^0x/i, '');
for (let i = 0; i < 40; i++) {
// the nth letter should be uppercase if the nth digit of casemap is 1
if ((parseInt(addressHash[i], 16) > 7 && origin[i].toUpperCase() !== origin[i]) ||
(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 = mcrypto_1.Hasher.Keccak.hash256(lower).replace(/^0x/i, '');
let checksumAddress = '0x';
for (let i = 0; i < lower.length; i++) {
// If ith character is 8 to f then make it uppercase
if (parseInt(addressHash[i], 16) > 7) {
checksumAddress += lower[i].toUpperCase();
}
else {
checksumAddress += lower[i];
}
}
return checksumAddress;
};
exports.toChecksumAddress = toChecksumAddress;
/**
* 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 = (0, upperFirst_1.default)(`${mapping[x] || x}Type`);
// @ts-ignore
if (Object.values(mcrypto_1.types[key]).includes(input[x]) === false) {
throw new Error(`Unsupported ${x} type: ${input[x]}`);
}
});
const output = { role, pk, hash, address };
// Overwrite hash methods for some role type if we are not eth-type
if (isEthereumType(output) === false) {
const sha2Roles = [
mcrypto_1.types.RoleType.ROLE_NODE,
mcrypto_1.types.RoleType.ROLE_VALIDATOR,
mcrypto_1.types.RoleType.ROLE_TETHER,
mcrypto_1.types.RoleType.ROLE_SWAP,
];
if (sha2Roles.includes(output.role)) {
output.hash = mcrypto_1.types.HashType.SHA2;
}
}
return output;
}
DidType.toJSON = (type) => Object.keys(type).reduce((acc, x) => {
const key = (0, upperFirst_1.default)(`${mapping[x] || x}Type`);
// @ts-ignore
const typeStr = Object.keys(mcrypto_1.types[key]);
// @ts-ignore
const typeValues = Object.values(mcrypto_1.types[key]);
// @ts-ignore
acc[x] = typeStr[typeValues.indexOf(type[x])];
return acc;
}, {});
DidType.fromJSON = (json) => Object.keys(json).reduce((acc, x) => {
const key = (0, upperFirst_1.default)(`${mapping[x] || x}Type`);
// @ts-ignore
const typeStr = Object.keys(mcrypto_1.types[key]);
// @ts-ignore
const typeValues = Object.values(mcrypto_1.types[key]);
// @ts-ignore
acc[x] = typeValues[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 roleBits = (0, util_2.toBits)(info.role, 6);
const keyBits = (0, util_2.toBits)(info.pk, 5);
const hashBits = (0, util_2.toBits)(info.hash, 5);
const infoBits = `${roleBits}${keyBits}${hashBits}`;
const infoHex = (0, util_1.stripHexPrefix)((0, util_1.numberToHex)(parseInt(infoBits, 2)));
return (0, util_2.toStrictHex)(infoHex, 4);
};
exports.fromTypeInfo = fromTypeInfo;
/**
* Get type info from did
*/
const toTypeInfo = (did) => {
let type = {};
try {
if (isEthereumDid(did)) {
type = DID_TYPE_ETHEREUM;
}
else {
const bytes = (0, util_2.toBytes)(did);
const typeBytes = bytes.slice(0, 2);
const typeHex = (0, util_2.toStrictHex)(Buffer.from(typeBytes).toString('hex'));
const typeBits = (0, util_2.toBits)(new util_1.BN(typeHex, 16), 16);
const roleBits = typeBits.slice(0, 6);
const keyBits = typeBits.slice(6, 11);
const hashBits = typeBits.slice(11, 16);
type = {
role: parseInt(roleBits, 2),
pk: parseInt(keyBits, 2),
hash: parseInt(hashBits, 2),
address: (0, util_1.isHex)((0, util_1.toAddress)(did)) ? mcrypto_1.types.EncodingType.BASE16 : mcrypto_1.types.EncodingType.BASE58,
};
}
// remove unsupported types
Object.keys(type).forEach((x) => {
// @ts-ignore
const enums = Object.values(mcrypto_1.types[`${(0, upperFirst_1.default)(mapping[x] || x)}Type`]);
if (enums.includes(type[x]) === false) {
delete type[x];
}
});
return type;
}
catch {
return type;
}
};
exports.toTypeInfo = toTypeInfo;
const toTypeInfoStr = (did) => {
const type = toTypeInfo(did);
return Object.keys(type).reduce((acc, x) => {
// @ts-ignore
const enums = mcrypto_1.types[`${(0, upperFirst_1.default)(mapping[x] || x)}Type`];
// @ts-ignore
acc[x] = Object.keys(enums).find((d) => enums[d] === type[x]);
return acc;
}, {});
};
exports.toTypeInfoStr = toTypeInfoStr;