From 10554797d2ad97d00821d39348beeae125fb3bf8 Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Fri, 10 May 2024 14:37:56 +0200 Subject: [PATCH 1/8] implement vidos resolver --- src/state/vidosResolver.ts | 137 +++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 src/state/vidosResolver.ts diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts new file mode 100644 index 0000000..643f9ba --- /dev/null +++ b/src/state/vidosResolver.ts @@ -0,0 +1,137 @@ +import { Id } from '@iden3/js-iden3-core'; +import { type IStateResolver, type ResolvedState, isGenesisStateId } from './resolver'; + +type DidResolutionResult = { + didResolutionMetadata: unknown; + didDocumentMetadata: unknown; + didDocument: { + id: string; + alsoKnownAs: string[]; + controller: string; + verificationMethod: { + id: string; + type: string; + controller: string; + stateContractAddress: string; + published: boolean; + info: { + id: string; + state: string; + replacedByState: string; + createdAtTimestamp: string; + replacedAtTimestamp: string; + createdAtBlock: string; + replacedAtBlock: string; + }; + global: { + root: string; + replacedByRoot: string; + createdAtTimestamp: string; + replacedAtTimestamp: string; + createdAtBlock: string; + replacedAtBlock: string; + }; + }[]; + }; +}; + +/** + * Implementation of {@link IStateResolver} that uses Vidos resolver service to resolve states. + * It can serve as drop-in replacement for EthStateResolver. + */ +export default class VidosResolver implements IStateResolver { + constructor( + private readonly resolverUrl: string, + private readonly apiKey: string, + ) {} + + async rootResolve(state: bigint): Promise { + const stateHex = state.toString(16); + + const zeroAddress = '11111111111111111111'; // 1 is 0 in base58 + const did = `did:polygonid:polygon:amoy:${zeroAddress}?gist=${stateHex}`; + + const response = await fetch( + `${this.resolverUrl}/${encodeURIComponent(did)}`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }, + ); + const result = (await response.json()) as DidResolutionResult; + + const globalInfo = result.didDocument.verificationMethod[0].global; + if (globalInfo == null) throw new Error('gist info not found'); + + if (globalInfo.root !== stateHex) { + throw new Error('gist info contains invalid state'); + } + + if (globalInfo.replacedByRoot !== '0') { + if (globalInfo.replacedAtTimestamp === '0') { + throw new Error('state was replaced, but replaced time unknown'); + } + return { + latest: false, + state: state, + transitionTimestamp: globalInfo.replacedAtTimestamp, + genesis: false, + }; + } + + return { + latest: true, + state: state, + transitionTimestamp: 0, + genesis: false, + }; + } + + async resolve(id: bigint, state: bigint): Promise { + const iden3Id = Id.fromBigInt(id); + const stateHex = state.toString(16); + + const did = `did:polygonid:polygon:amoy:${iden3Id.string()}`; + + const didWithState = `${did}?state=${stateHex}`; + const response = await fetch( + `${this.resolverUrl}/${encodeURIComponent(didWithState)}`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, + }, + ); + const result = await response.json(); + const isGenesis = isGenesisStateId(id, state); + + const stateInfo = result.didDocument.verificationMethod[0].info; + if (stateInfo == null) throw new Error('state info not found'); + + if (stateInfo.id !== did) { + throw new Error(`state was recorded for another identity`); + } + + if (stateInfo.state !== stateHex) { + if (stateInfo.replacedAtTimestamp === '0') { + throw new Error(`no information about state transition`); + } + return { + latest: false, + genesis: false, + state: state, + transitionTimestamp: Number.parseInt(stateInfo.replacedAtTimestamp), + }; + } + + return { + latest: stateInfo.replacedAtTimestamp === '0', + genesis: isGenesis, + state, + transitionTimestamp: Number.parseInt(stateInfo.replacedAtTimestamp), + }; + } +} \ No newline at end of file From 6f75f72c3ccb0c0417efebd6a558370dfa2feecc Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 13:14:48 +0200 Subject: [PATCH 2/8] update import --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index f738d8b..e6d8fb3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ export * as auth from '@lib/auth/auth'; export * as resolver from '@lib/state/resolver'; +export { default as VidosResolver } from '@lib/state/vidosResolver'; export * as protocol from '@lib/types-sdk'; export { core } from '@0xpolygonid/js-sdk'; From ab874484fdb0f6b65f78bd534bcf04502c8f6e2a Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 13:15:10 +0200 Subject: [PATCH 3/8] fix formatting --- src/state/vidosResolver.ts | 45 +++++++++++++++----------------------- 1 file changed, 18 insertions(+), 27 deletions(-) diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts index 643f9ba..5ed48de 100644 --- a/src/state/vidosResolver.ts +++ b/src/state/vidosResolver.ts @@ -40,10 +40,7 @@ type DidResolutionResult = { * It can serve as drop-in replacement for EthStateResolver. */ export default class VidosResolver implements IStateResolver { - constructor( - private readonly resolverUrl: string, - private readonly apiKey: string, - ) {} + constructor(private readonly resolverUrl: string, private readonly apiKey: string) {} async rootResolve(state: bigint): Promise { const stateHex = state.toString(16); @@ -51,15 +48,12 @@ export default class VidosResolver implements IStateResolver { const zeroAddress = '11111111111111111111'; // 1 is 0 in base58 const did = `did:polygonid:polygon:amoy:${zeroAddress}?gist=${stateHex}`; - const response = await fetch( - `${this.resolverUrl}/${encodeURIComponent(did)}`, - { - method: 'GET', - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - }, - ); + const response = await fetch(`${this.resolverUrl}/${encodeURIComponent(did)}`, { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}` + } + }); const result = (await response.json()) as DidResolutionResult; const globalInfo = result.didDocument.verificationMethod[0].global; @@ -77,7 +71,7 @@ export default class VidosResolver implements IStateResolver { latest: false, state: state, transitionTimestamp: globalInfo.replacedAtTimestamp, - genesis: false, + genesis: false }; } @@ -85,7 +79,7 @@ export default class VidosResolver implements IStateResolver { latest: true, state: state, transitionTimestamp: 0, - genesis: false, + genesis: false }; } @@ -96,15 +90,12 @@ export default class VidosResolver implements IStateResolver { const did = `did:polygonid:polygon:amoy:${iden3Id.string()}`; const didWithState = `${did}?state=${stateHex}`; - const response = await fetch( - `${this.resolverUrl}/${encodeURIComponent(didWithState)}`, - { - method: 'GET', - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - }, - ); + const response = await fetch(`${this.resolverUrl}/${encodeURIComponent(didWithState)}`, { + method: 'GET', + headers: { + Authorization: `Bearer ${this.apiKey}` + } + }); const result = await response.json(); const isGenesis = isGenesisStateId(id, state); @@ -123,7 +114,7 @@ export default class VidosResolver implements IStateResolver { latest: false, genesis: false, state: state, - transitionTimestamp: Number.parseInt(stateInfo.replacedAtTimestamp), + transitionTimestamp: Number.parseInt(stateInfo.replacedAtTimestamp) }; } @@ -131,7 +122,7 @@ export default class VidosResolver implements IStateResolver { latest: stateInfo.replacedAtTimestamp === '0', genesis: isGenesis, state, - transitionTimestamp: Number.parseInt(stateInfo.replacedAtTimestamp), + transitionTimestamp: Number.parseInt(stateInfo.replacedAtTimestamp) }; } -} \ No newline at end of file +} From a6c0e58ec50aceeefdbb8eb5a4a089a715bfe17a Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 13:27:33 +0200 Subject: [PATCH 4/8] add impl note --- src/state/vidosResolver.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts index 5ed48de..d8378b4 100644 --- a/src/state/vidosResolver.ts +++ b/src/state/vidosResolver.ts @@ -42,6 +42,9 @@ type DidResolutionResult = { export default class VidosResolver implements IStateResolver { constructor(private readonly resolverUrl: string, private readonly apiKey: string) {} + // Note: implementation closely resembles EthStateResolver because Vidos resolver internally uses the same contract. + // The only difference is the usage of regular HTTP requests instead of web3 calls. + async rootResolve(state: bigint): Promise { const stateHex = state.toString(16); From 156be16a3f6d627250e935214f108d8b092d2395 Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 13:40:08 +0200 Subject: [PATCH 5/8] fix prettier --- src/state/vidosResolver.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts index d8378b4..2f99ff4 100644 --- a/src/state/vidosResolver.ts +++ b/src/state/vidosResolver.ts @@ -42,7 +42,7 @@ type DidResolutionResult = { export default class VidosResolver implements IStateResolver { constructor(private readonly resolverUrl: string, private readonly apiKey: string) {} - // Note: implementation closely resembles EthStateResolver because Vidos resolver internally uses the same contract. + // Note: implementation closely resembles EthStateResolver because Vidos resolver internally uses the same contract. // The only difference is the usage of regular HTTP requests instead of web3 calls. async rootResolve(state: bigint): Promise { From fa1ed4c11cb5d895e5d19b3b3221dfcba98e7a55 Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 15:23:14 +0200 Subject: [PATCH 6/8] use did-resolver type --- package-lock.json | 1 + package.json | 3 ++- src/state/vidosResolver.ts | 24 +++++++++--------------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3435db..1e6349e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", + "did-resolver": "4.1.0", "eslint": "^8.13.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", diff --git a/package.json b/package.json index 44b9ab8..1610194 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "tsc-alias": "^1.8.8", "tsconfig-paths": "^3.14.2", "typechain": "^8.1.1", - "typescript": "^4.3.5" + "typescript": "^4.3.5", + "did-resolver": "4.1.0" } } diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts index 2f99ff4..0ab6b16 100644 --- a/src/state/vidosResolver.ts +++ b/src/state/vidosResolver.ts @@ -1,19 +1,13 @@ import { Id } from '@iden3/js-iden3-core'; import { type IStateResolver, type ResolvedState, isGenesisStateId } from './resolver'; +import type { DIDDocument, DIDResolutionResult, VerificationMethod } from 'did-resolver'; -type DidResolutionResult = { - didResolutionMetadata: unknown; - didDocumentMetadata: unknown; - didDocument: { - id: string; - alsoKnownAs: string[]; - controller: string; - verificationMethod: { - id: string; - type: string; - controller: string; - stateContractAddress: string; - published: boolean; +/** + * Extended DID resolution result that includes additional information about Polygon ID resolution. + */ +type PolygonDidResolutionResult = DIDResolutionResult & { + didDocument: DIDDocument & { + verificationMethod: (VerificationMethod & { info: { id: string; state: string; @@ -31,7 +25,7 @@ type DidResolutionResult = { createdAtBlock: string; replacedAtBlock: string; }; - }[]; + })[]; }; }; @@ -57,7 +51,7 @@ export default class VidosResolver implements IStateResolver { Authorization: `Bearer ${this.apiKey}` } }); - const result = (await response.json()) as DidResolutionResult; + const result = (await response.json()) as PolygonDidResolutionResult; const globalInfo = result.didDocument.verificationMethod[0].global; if (globalInfo == null) throw new Error('gist info not found'); From cc4047c480cf7bf5792a9a4fbbaac0604257a575 Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 15:55:19 +0200 Subject: [PATCH 7/8] add network config --- src/state/vidosResolver.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts index 0ab6b16..7829dcd 100644 --- a/src/state/vidosResolver.ts +++ b/src/state/vidosResolver.ts @@ -1,3 +1,4 @@ +/* eslint-disable prettier/prettier */ import { Id } from '@iden3/js-iden3-core'; import { type IStateResolver, type ResolvedState, isGenesisStateId } from './resolver'; import type { DIDDocument, DIDResolutionResult, VerificationMethod } from 'did-resolver'; @@ -34,7 +35,7 @@ type PolygonDidResolutionResult = DIDResolutionResult & { * It can serve as drop-in replacement for EthStateResolver. */ export default class VidosResolver implements IStateResolver { - constructor(private readonly resolverUrl: string, private readonly apiKey: string) {} + constructor(private readonly resolverUrl: string, private readonly apiKey: string, private readonly network: 'main' | 'mumbai' | 'amoy' = 'main') {} // Note: implementation closely resembles EthStateResolver because Vidos resolver internally uses the same contract. // The only difference is the usage of regular HTTP requests instead of web3 calls. @@ -43,7 +44,7 @@ export default class VidosResolver implements IStateResolver { const stateHex = state.toString(16); const zeroAddress = '11111111111111111111'; // 1 is 0 in base58 - const did = `did:polygonid:polygon:amoy:${zeroAddress}?gist=${stateHex}`; + const did = `did:polygonid:polygon:${this.network}:${zeroAddress}?gist=${stateHex}`; const response = await fetch(`${this.resolverUrl}/${encodeURIComponent(did)}`, { method: 'GET', @@ -84,7 +85,7 @@ export default class VidosResolver implements IStateResolver { const iden3Id = Id.fromBigInt(id); const stateHex = state.toString(16); - const did = `did:polygonid:polygon:amoy:${iden3Id.string()}`; + const did = `did:polygonid:polygon:${this.network}:${iden3Id.string()}`; const didWithState = `${did}?state=${stateHex}`; const response = await fetch(`${this.resolverUrl}/${encodeURIComponent(didWithState)}`, { From 7165914f3cad8616aaebc61433fe090c5003dd1f Mon Sep 17 00:00:00 2001 From: Antonio Ivanovski Date: Mon, 13 May 2024 16:04:27 +0200 Subject: [PATCH 8/8] throw error on did resolution metadata error --- src/state/vidosResolver.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/state/vidosResolver.ts b/src/state/vidosResolver.ts index 7829dcd..1335cbf 100644 --- a/src/state/vidosResolver.ts +++ b/src/state/vidosResolver.ts @@ -53,6 +53,9 @@ export default class VidosResolver implements IStateResolver { } }); const result = (await response.json()) as PolygonDidResolutionResult; + if (result.didResolutionMetadata.error) { + throw new Error(`error resolving DID: ${result.didResolutionMetadata.error}`); + } const globalInfo = result.didDocument.verificationMethod[0].global; if (globalInfo == null) throw new Error('gist info not found'); @@ -94,7 +97,11 @@ export default class VidosResolver implements IStateResolver { Authorization: `Bearer ${this.apiKey}` } }); - const result = await response.json(); + const result = (await response.json()) as PolygonDidResolutionResult; + if (result.didResolutionMetadata.error) { + throw new Error(`error resolving DID: ${result.didResolutionMetadata.error}`); + } + const isGenesis = isGenesisStateId(id, state); const stateInfo = result.didDocument.verificationMethod[0].info;