diff --git a/package-lock.json b/package-lock.json index 6c453a1..b1df681 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@iden3/js-iden3-core", - "version": "1.1.0", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@iden3/js-iden3-core", - "version": "1.1.0", + "version": "1.2.0", "license": "AGPL-3.0", "devDependencies": { "@iden3/eslint-config": "https://github.com/iden3/eslint-config", diff --git a/package.json b/package.json index b56a0a1..d58eefb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@iden3/js-iden3-core", - "version": "1.1.0", + "version": "1.2.0", "description": "Low level API to create and manipulate iden3 Claims.", "source": "./src/index.ts", "typings": "dist/types/index.d.ts", diff --git a/src/constants.ts b/src/constants.ts index f905974..2eb4df7 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -42,41 +42,55 @@ export const Constants = Object.freeze({ GENESIS_LENGTH: 27 }); -export enum Blockchain { - Ethereum = 'eth', - Polygon = 'polygon', - ZkEVM = 'zkevm', - Unknown = 'unknown', - NoChain = '', - ReadOnly = 'readonly' -} +export const Blockchain: { [k: string]: string } = { + Ethereum: 'eth', + Polygon: 'polygon', + ZkEVM: 'zkevm', + Unknown: 'unknown', + NoChain: '', + ReadOnly: 'readonly' +}; -export enum NetworkId { - Main = 'main', - Mumbai = 'mumbai', - Goerli = 'goerli', - Sepolia = 'sepolia', - Test = 'test', - Unknown = 'unknown', - NoNetwork = '' -} +export const NetworkId: { [k: string]: string } = { + Main: 'main', + Mumbai: 'mumbai', + Goerli: 'goerli', + Sepolia: 'sepolia', + Test: 'test', + Unknown: 'unknown', + NoNetwork: '' +}; -export enum DidMethod { - Iden3 = 'iden3', - PolygonId = 'polygonid', - Other = '' -} +export const DidMethod: { [k: string]: string } = { + Iden3: 'iden3', + PolygonId: 'polygonid', + Other: '' +}; -export const DidMethodByte: { [key: string]: number } = Object.freeze({ +/** + * Object containing chain IDs for various blockchains and networks. + * @type { [key: string]: number } + */ +export const ChainIds: { [key: string]: number } = { + [`${Blockchain.Ethereum}:${NetworkId.Main}`]: 1, + [`${Blockchain.Ethereum}:${NetworkId.Goerli}`]: 5, + [`${Blockchain.Ethereum}:${NetworkId.Sepolia}`]: 11155111, + [`${Blockchain.Polygon}:${NetworkId.Main}`]: 137, + [`${Blockchain.Polygon}:${NetworkId.Mumbai}`]: 80001, + [`${Blockchain.ZkEVM}:${NetworkId.Main}`]: 1101, + [`${Blockchain.ZkEVM}:${NetworkId.Test}`]: 1442 +}; + +export const DidMethodByte: { [key: string]: number } = { [DidMethod.Iden3]: 0b00000001, [DidMethod.PolygonId]: 0b00000010, [DidMethod.Other]: 0b11111111 -}); +}; // DIDMethodNetwork is map for did methods and their blockchain networks export const DidMethodNetwork: { [k: string]: { [k: string]: number }; -} = Object.freeze({ +} = { [DidMethod.Iden3]: { [`${Blockchain.ReadOnly}:${NetworkId.NoNetwork}`]: 0b00000000, [`${Blockchain.Polygon}:${NetworkId.Main}`]: 0b00010000 | 0b00000001, @@ -100,4 +114,4 @@ export const DidMethodNetwork: { [DidMethod.Other]: { [`${Blockchain.Unknown}:${NetworkId.Unknown}`]: 0b11111111 } -}); +}; diff --git a/src/did/did-helper.ts b/src/did/did-helper.ts index c0a68d4..d4d52d6 100644 --- a/src/did/did-helper.ts +++ b/src/did/did-helper.ts @@ -1,15 +1,8 @@ -import { - Blockchain, - Constants, - DidMethodByte, - DidMethodNetwork, - DidMethod, - NetworkId -} from '../constants'; +import { Constants, DidMethodByte, DidMethodNetwork } from '../constants'; // DIDNetworkFlag is a structure to represent DID blockchain and network id export class DIDNetworkFlag { - constructor(public readonly blockchain: Blockchain, public readonly networkId: NetworkId) {} + constructor(public readonly blockchain: string, public readonly networkId: string) {} toString(): string { return `${this.blockchain}:${this.networkId}`; @@ -17,19 +10,12 @@ export class DIDNetworkFlag { static fromString(s: string): DIDNetworkFlag { const [blockchain, networkId] = s.split(':'); - return new DIDNetworkFlag( - blockchain.replace('_', '') as Blockchain, - networkId.replace('_', '') as NetworkId - ); + return new DIDNetworkFlag(blockchain.replace('_', ''), networkId.replace('_', '')); } } // BuildDIDType builds bytes type from chain and network -export function buildDIDType( - method: DidMethod, - blockchain: Blockchain, - network: NetworkId -): Uint8Array { +export function buildDIDType(method: string, blockchain: string, network: string): Uint8Array { const fb = DidMethodByte[method]; if (!fb) { throw Constants.ERRORS.UNSUPPORTED_DID_METHOD; @@ -53,7 +39,7 @@ export function buildDIDType( } // FindNetworkIDForDIDMethodByValue finds network by byte value -export function findNetworkIDForDIDMethodByValue(method: DidMethod, byteNumber: number): NetworkId { +export function findNetworkIDForDIDMethodByValue(method: string, byteNumber: number): string { const methodMap = DidMethodNetwork[method]; if (!methodMap) { throw Constants.ERRORS.UNSUPPORTED_DID_METHOD; @@ -67,10 +53,7 @@ export function findNetworkIDForDIDMethodByValue(method: DidMethod, byteNumber: } // findBlockchainForDIDMethodByValue finds blockchain type by byte value -export function findBlockchainForDIDMethodByValue( - method: DidMethod, - byteNumber: number -): Blockchain { +export function findBlockchainForDIDMethodByValue(method: string, byteNumber: number): string { const methodMap = DidMethodNetwork[method]; if (!methodMap) { throw new Error( @@ -86,10 +69,10 @@ export function findBlockchainForDIDMethodByValue( } // findDIDMethodByValue finds did method by its byte value -export function findDIDMethodByValue(byteNumber: number): DidMethod { +export function findDIDMethodByValue(byteNumber: number): string { for (const [key, value] of Object.entries(DidMethodByte)) { if (value === byteNumber) { - return key as DidMethod; + return key; } } throw Constants.ERRORS.UNSUPPORTED_DID_METHOD; diff --git a/src/did/did.ts b/src/did/did.ts index 3669e53..7d4a1aa 100644 --- a/src/did/did.ts +++ b/src/did/did.ts @@ -107,9 +107,9 @@ export class DID { } static decodePartsFromId(id: Id): { - method: DidMethod; - blockchain: Blockchain; - networkId: NetworkId; + method: string; + blockchain: string; + networkId: string; } { const method = findDIDMethodByValue(id.bytes[0]); const blockchain = findBlockchainForDIDMethodByValue(method, id.bytes[1]); @@ -119,22 +119,22 @@ export class DID { return { method, blockchain, networkId }; } - static networkIdFromId(id: Id): NetworkId { + static networkIdFromId(id: Id): string { return DID.throwIfDIDUnsupported(id).networkId; } - static methodFromId(id: Id): DidMethod { + static methodFromId(id: Id): string { return DID.throwIfDIDUnsupported(id).method; } - static blockchainFromId(id: Id): Blockchain { + static blockchainFromId(id: Id): string { return DID.throwIfDIDUnsupported(id).blockchain; } private static throwIfDIDUnsupported(id: Id): { - method: DidMethod; - blockchain: Blockchain; - networkId: NetworkId; + method: string; + blockchain: string; + networkId: string; } { const { method, blockchain, networkId } = DID.decodePartsFromId(id); @@ -191,7 +191,7 @@ export class DID { return id; } - static isUnsupported(method: DidMethod, blockchain: Blockchain, networkId: NetworkId): boolean { + static isUnsupported(method: string, blockchain: string, networkId: string): boolean { return ( method == DidMethod.Other && blockchain == Blockchain.Unknown && diff --git a/src/index.ts b/src/index.ts index 042fe84..489d451 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,3 +5,4 @@ export * from './elemBytes'; export * from './id'; export * from './schemaHash'; export * from './utils'; +export * from './registration'; diff --git a/src/registration.ts b/src/registration.ts new file mode 100644 index 0000000..0c06f1f --- /dev/null +++ b/src/registration.ts @@ -0,0 +1,157 @@ +import { + Blockchain, + ChainIds, + DidMethod, + DidMethodByte, + DidMethodNetwork, + NetworkId +} from './constants'; +import { DID } from './did'; + +export const registerBlockchain = (blockchain: string): void => { + Blockchain[blockchain] = blockchain; +}; + +export const registerNetwork = (network: string): void => { + NetworkId[network] = network; +}; + +export const registerDidMethod = (method: string, byte: number): void => { + const existingByte = DidMethodByte[method]; + if (typeof DidMethodByte[method] === 'number' && existingByte !== byte) { + throw new Error( + `DID method '${method}' already registered with byte '${existingByte.toString(2)}'` + ); + } + const max = DidMethodByte[DidMethod.Other]; + + if (byte >= max) { + throw new Error( + `Can't register DID method byte: current '${byte.toString(2)}', maximum byte allowed: '${( + max - 1 + ).toString(2)}'` + ); + } + + DidMethod[method] = method; + DidMethodByte[method] = byte; +}; + +/** + * Register chain ID for a blockchain and network. + * + * @param {string} blockchain + * @param {string} network + * @param {number} [chainId] + * @returns {void} + */ +export const registerChainId = (blockchain: string, network: string, chainId: number): void => { + const key = `${blockchain}:${network}`; + + if (typeof ChainIds[key] === 'number' && ChainIds[key] !== chainId) { + throw new Error( + `chainId '${blockchain}:${network}' already registered with value '${ChainIds[key]}'` + ); + } + + ChainIds[key] = chainId; +}; + +/** + * Get chain ID by a blockchain and network. + * + * @param {string} blockchain + * @param {string} [network] + * @returns {number} + */ +export const getChainId = (blockchain: string, network?: string): number => { + if (network) { + blockchain += `:${network}`; + } + const chainId = ChainIds[blockchain]; + if (!chainId) { + throw new Error(`chainId not found for ${blockchain}`); + } + return chainId; +}; + +/** + * ChainIDfromDID returns chain name from w3c.DID + * + * @param {DID} did + * @returns {number} + */ +export const chainIDfromDID = (did: DID): number => { + const id = DID.idFromDID(did); + + const blockchain = DID.blockchainFromId(id); + + const networkId = DID.networkIdFromId(id); + + const chainId = ChainIds[`${blockchain}:${networkId}`]; + if (typeof chainId !== 'number') { + throw new Error(`chainId not found for ${blockchain}:${networkId}`); + } + + return chainId; +}; + +/** + * Register a DID method with a byte value. + * https://docs.iden3.io/getting-started/identity/identity-types/#regular-identity + * @param {{ + * method: DidMethodName; DID method name + * methodByte?: number; put DID method byte value in case you want to register new DID method + * blockchain: BlockchainName; blockchain name + * network: NetworkName; network name + * networkFlag: number; network flag + * chainId?: number; put chain ID in case you need to use it + * }} { + * method, + * methodByte, + * blockchain, + * network, + * chainId, + * networkFlag + * } + */ +export const registerDidMethodNetwork = ({ + method, + methodByte, + blockchain, + network, + chainId, + networkFlag +}: { + method: string; + methodByte?: number; + blockchain: string; + network: string; + networkFlag: number; + chainId?: number; +}): void => { + registerBlockchain(blockchain); + registerNetwork(network); + if (typeof methodByte === 'number') { + registerDidMethod(method, methodByte); + } + + if (!DidMethodNetwork[method]) { + DidMethodNetwork[method] = {}; + } + + if (typeof chainId === 'number') { + registerChainId(blockchain, network, chainId); + } + + const key = `${blockchain}:${network}`; + const existedFlag = DidMethodNetwork[method][key]; + if (typeof existedFlag === 'number' && existedFlag !== networkFlag) { + throw new Error( + `DID method network '${method}' with blockchain '${blockchain}' and network '${network}' already registered with another flag '${existedFlag.toString( + 2 + )}'` + ); + } + DidMethodNetwork[method][key] = networkFlag; +}; diff --git a/tests/did.test.ts b/tests/did.test.ts index ab4fbfa..c96e492 100644 --- a/tests/did.test.ts +++ b/tests/did.test.ts @@ -3,11 +3,12 @@ import { DID, buildDIDType } from '../src/did'; import { Id } from '../src/id'; import { Blockchain, DidMethodByte, DidMethod, NetworkId, Constants } from './../src/constants'; import { genesisFromEthAddress } from '../src/utils'; +import { registerDidMethodNetwork } from '../src/registration'; export const helperBuildDIDFromType = ( - method: DidMethod, - blockchain: Blockchain, - network: NetworkId + method: string, + blockchain: string, + network: string ): DID => { const typ = buildDIDType(method, blockchain, network); return DID.newFromIdenState(typ, 1n); @@ -254,4 +255,159 @@ describe('DID tests', () => { "can't get Ethereum address: high bytes of genesis are not zero" ); }); + + it('TestCustomDIDRegistration', () => { + const testCases = [ + { + description: 'register new did method network', + data: { + method: 'test_method', + blockchain: 'test_chain', + network: 'test_net', + networkFlag: 0b0001_0001, + chainId: 101, + methodByte: 0b00000011 + } + }, + { + description: 'register one more new did method network', + data: { + method: 'method', + blockchain: 'chain', + network: 'network', + networkFlag: 0b0001_0001, + chainId: 102, + methodByte: 0b00000100 + } + }, + { + description: 'register the same new did method network', + data: { + method: 'method', + blockchain: 'chain', + network: 'network', + networkFlag: 0b0001_0001, + chainId: 102, + methodByte: 0b00000100 + } + }, + { + description: 'register network to existing did method', + data: { + method: DidMethod.Iden3, + blockchain: 'chain', + network: NetworkId.Test, + networkFlag: 0b01000000 | 0b00000011, + chainId: 103 + } + }, + { + description: 'register network to existing did method and chainId', + data: { + method: DidMethod.Iden3, + blockchain: Blockchain.ReadOnly, + network: NetworkId.NoNetwork, + networkFlag: 0b00000000, + chainId: 103 + } + }, + { + description: 'register one more network to existing did method', + data: { + method: DidMethod.Iden3, + blockchain: Blockchain.ReadOnly, + network: 'network', + networkFlag: 0b01000000 | 0b00000011, + chainId: 104 + } + }, + { + description: 'register known chain id to new did method', + data: { + method: 'method2', + blockchain: Blockchain.Polygon, + network: NetworkId.Mumbai, + networkFlag: 0b0001_0001, + methodByte: 0b0000001 + } + } + ]; + + for (let i = 0; i < testCases.length; i++) { + const tc = testCases[i]; + expect(() => registerDidMethodNetwork(tc.data)).not.toThrow(); + } + + const d = helperBuildDIDFromType('method', 'chain', 'network'); + // const did = helperBuildDIDFromType('method', 'chain', 'network'); + expect('4bb86obLkMrifHixMY62WM4iQQVr7u29cxWjMAinrT').toEqual(d.string().split(':').pop()); + + // did + const didStr = 'did:method:chain:network:4bb86obLkMrifHixMY62WM4iQQVr7u29cxWjMAinrT'; + + const did3 = DID.parse(didStr); + const id = DID.idFromDID(did3); + + expect('4bb86obLkMrifHixMY62WM4iQQVr7u29cxWjMAinrT').toEqual(id.string()); + const method = DID.methodFromId(id); + expect(DidMethod.method).toBe(method); + const blockchain = DID.blockchainFromId(id); + expect(Blockchain.chain).toBe(blockchain); + const networkId = DID.networkIdFromId(id); + expect(NetworkId.network).toBe(networkId); + }); + + const testCases = [ + { + description: 'try to overwrite existing chain id', + data: { + method: DidMethod.Iden3, + blockchain: Blockchain.Polygon, + network: NetworkId.Mumbai, + networkFlag: 0b0001_0001, + chainId: 1 + }, + err: "chainId 'polygon:mumbai' already registered with value '80001'" + }, + { + description: 'try to overwrite existing DID method byte', + data: { + method: DidMethod.Iden3, + blockchain: Blockchain.Ethereum, + network: NetworkId.Main, + networkFlag: 0b00100000 | 0b00000001, + chainId: 1, + methodByte: 0b00000010 + }, + err: "DID method 'iden3' already registered with byte '1'" + }, + { + description: 'try to write max did method byte', + data: { + method: 'method33', + blockchain: Blockchain.Ethereum, + network: NetworkId.Main, + networkFlag: 0b00100000 | 0b00000001, + chainId: 1, + methodByte: 0b11111111 + }, + err: "Can't register DID method byte: current '11111111', maximum byte allowed: '11111110'" + }, + { + description: 'try to rewrite existing DID Method Network Flag', + data: { + method: DidMethod.Iden3, + blockchain: Blockchain.Ethereum, + network: NetworkId.Main, + networkFlag: 0b00100000 | 0b00000011 + }, + err: "DID method network 'iden3' with blockchain 'eth' and network 'main' already registered with another flag '100001'" + } + ]; + for (let i = 0; i < testCases.length; i++) { + const tc = testCases[i]; + it(tc.description, () => { + expect(() => registerDidMethodNetwork(tc.data)).toThrowError(tc.err); + }); + } });