diff --git a/README.md b/README.md index 89721015..3d0f28c2 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ npm install @govtechsg/oa-verify import { documentRopstenValidWithToken } from "./test/fixtures/v2/documentRopstenValidWithToken"; import { verify, isValid } from "@govtechsg/oa-verify"; -const fragments = await verify(documentRopstenValidWithToken, { network: "ropsten" }); +const fragments = await verify(documentRopstenValidWithToken); console.log(fragments); // see below console.log(isValid(fragments)); // display true ``` @@ -82,9 +82,27 @@ console.log(isValid(fragments)); // display true ### Environment Variables -- `ETHEREUM_PROVIDER`: let you pick the provider you want to use. Available values: `cloudflare`. The provider will default to `infura` if the variable is not set. - `INFURA_API_KEY`: let you provide your own `INFURA` API key. +### Switching network + +You may build the verifier to verify against a custom network by either: + +1. providing your own web3 provider +2. specifying the network name (provider will be using the default ones) + +To provide your own provider: + +```ts +const verify = verificationBuilder(openAttestationVerifiers, { provider: customProvider }); +``` + +To specify network: + +```ts +const verify = verificationBuilder(openAttestationVerifiers, { network: "ropsten" }); +``` + ### Verify By default the provided `verify` method performs multiple checks on a document diff --git a/src/common/utils.ts b/src/common/utils.ts index 96661220..0a70b36a 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,7 +1,11 @@ -import * as ethers from "ethers"; +import { providers } from "ethers"; +import { VerificationBuilderOptions, VerificationBuilderOptionsWithNetwork } from "../types/core"; import { INFURA_API_KEY } from "../config"; -export const getProvider = (options: { network: string }): ethers.providers.Provider => - process.env.ETHEREUM_PROVIDER === "cloudflare" - ? new ethers.providers.CloudflareProvider() - : new ethers.providers.InfuraProvider(options.network, INFURA_API_KEY); +export const getDefaultProvider = (options: VerificationBuilderOptionsWithNetwork): providers.Provider => { + return new providers.InfuraProvider(options.network, INFURA_API_KEY); +}; + +export const getProvider = (options: VerificationBuilderOptions): providers.Provider => { + return "provider" in options ? options.provider : getDefaultProvider(options); +}; diff --git a/src/index.ts b/src/index.ts index cb0d5f03..6bacdbea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,11 +19,15 @@ const openAttestationVerifiers: Verifiers[] = [ openAttestationDnsDidIdentityProof, ]; +const defaultBuilderOption = { + network: "homestead", +}; + const verify = verificationBuilder< | SignedWrappedDocument | WrappedDocument | WrappedDocument ->(openAttestationVerifiers); +>(openAttestationVerifiers, defaultBuilderOption); export * from "./types/core"; export * from "./types/error"; diff --git a/src/types/core.ts b/src/types/core.ts index b16084f1..c7caa6c7 100644 --- a/src/types/core.ts +++ b/src/types/core.ts @@ -1,4 +1,5 @@ import { SignedWrappedDocument, v2, v3, WrappedDocument } from "@govtechsg/open-attestation"; +import { providers } from "ethers"; import { Reason } from "./error"; type DeepPartial = { @@ -6,12 +7,22 @@ type DeepPartial = { }; /** - * - network on which to run the verification (if needed to connect to ethereum), For instance "ropste" or "homestead" - * - promisesCallback callback function that will provide back the promises resolving to the verification fragment. It will be called before the promises are all resolved and thus give the possibility to consumers to perform their own extra checks. + * Callback function that will provide back the promises resolving to the verification fragment. It will be called before the promises are all resolved and thus give the possibility to consumers to perform their own extra checks. */ -export interface VerificationManagerOptions { +export type PromiseCallback = (promises: Promise[]) => void; + +export interface VerificationBuilderOptionsWithProvider { + provider: providers.Provider; +} + +export interface VerificationBuilderOptionsWithNetwork { network: string; - promisesCallback?: (promises: Promise[]) => void; +} + +export type VerificationBuilderOptions = VerificationBuilderOptionsWithProvider | VerificationBuilderOptionsWithNetwork; + +export interface VerifierOptions { + provider: providers.Provider; } /** @@ -54,7 +65,7 @@ interface SkippedVerificationFragment extends VerificationFragment { } export interface Verifier< Document = WrappedDocument | WrappedDocument, - Options = VerificationManagerOptions, + Options = VerifierOptions, Data = any > { skip: (document: Document, options: Options) => Promise; diff --git a/src/verifiers/documentIntegrity/hash/openAttestationHash.test.ts b/src/verifiers/documentIntegrity/hash/openAttestationHash.test.ts index b405e862..c1b3248a 100644 --- a/src/verifiers/documentIntegrity/hash/openAttestationHash.test.ts +++ b/src/verifiers/documentIntegrity/hash/openAttestationHash.test.ts @@ -3,7 +3,7 @@ import { tamperedDocumentWithCertificateStore } from "../../../../test/fixtures/ import { document } from "../../../../test/fixtures/v2/document"; import { verificationBuilder } from "../../verificationBuilder"; -const verify = verificationBuilder([openAttestationHash]); +const verify = verificationBuilder([openAttestationHash], { network: "ropsten" }); describe("OpenAttestationHash", () => { it("should return a skipped fragment when document is missing target hash", async () => { @@ -12,9 +12,7 @@ describe("OpenAttestationHash", () => { signature: { ...tamperedDocumentWithCertificateStore.signature }, }; delete newDocument.signature.targetHash; - const fragment = await verify(newDocument, { - network: "ropsten", - }); + const fragment = await verify(newDocument); expect(fragment).toStrictEqual([ { name: "OpenAttestationHash", @@ -34,9 +32,7 @@ describe("OpenAttestationHash", () => { signature: { ...tamperedDocumentWithCertificateStore.signature }, }; delete newDocument.signature.merkleRoot; - const fragment = await verify(newDocument, { - network: "ropsten", - }); + const fragment = await verify(newDocument); expect(fragment).toStrictEqual([ { name: "OpenAttestationHash", @@ -56,9 +52,7 @@ describe("OpenAttestationHash", () => { data: { ...tamperedDocumentWithCertificateStore.data }, }; delete newDocument.data; - const fragment = await verify(newDocument, { - network: "ropsten", - }); + const fragment = await verify(newDocument); expect(fragment).toStrictEqual([ { name: "OpenAttestationHash", @@ -74,7 +68,7 @@ describe("OpenAttestationHash", () => { }); it("should return an invalid fragment when document has been tampered", async () => { - const fragment = await verify(tamperedDocumentWithCertificateStore, { network: "" }); + const fragment = await verify(tamperedDocumentWithCertificateStore); expect(fragment).toStrictEqual([ { name: "OpenAttestationHash", @@ -90,7 +84,7 @@ describe("OpenAttestationHash", () => { ]); }); it("should return a valid fragment when document has not been tampered", async () => { - const fragment = await verify(document, { network: "" }); + const fragment = await verify(document); expect(fragment).toStrictEqual([ { name: "OpenAttestationHash", diff --git a/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.test.ts b/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.test.ts index ae73877a..ba66c919 100644 --- a/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.test.ts +++ b/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.test.ts @@ -7,6 +7,7 @@ import { documentDidMissingProof } from "../../../../test/fixtures/v2/documentDi import { documentRopstenNotIssuedWithTokenRegistry } from "../../../../test/fixtures/v2/documentRopstenNotIssuedWithTokenRegistry"; import { documentDidObfuscatedRevocation } from "../../../../test/fixtures/v2/documentDidObfuscatedRevocation"; import { getPublicKey } from "../../../did/resolver"; +import { getProvider } from "../../../common/utils"; jest.mock("../../../did/resolver"); @@ -24,9 +25,10 @@ const whenPublicKeyResolvesSuccessfully = () => { }); }; -// TODO Temporarily passing in this option, until make the entire option optional in another PR const options = { - network: "ropsten", + provider: getProvider({ + network: "ropsten", + }), }; describe("skip", () => { diff --git a/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.ts b/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.ts index 5b750fa7..16e06fa3 100644 --- a/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.ts +++ b/src/verifiers/documentStatus/didSigned/didSignedDocumentStatus.ts @@ -63,7 +63,12 @@ const verify: VerifierType["verify"] = withCodedErrorHandler( "UNSIGNED" ); const signatureVerificationDeferred: Promise[] = issuers.map((issuer) => - verifySignature({ merkleRoot, identityProof: issuer.identityProof, proof: document.proof, did: issuer.id }) + verifySignature({ + merkleRoot, + identityProof: issuer.identityProof, + proof: document.proof, + did: issuer.id, + }) ); const issuance = await (await Promise.all(signatureVerificationDeferred)).map((status) => { return status.verified diff --git a/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.test.ts b/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.test.ts index 4d0eef86..84f42c07 100644 --- a/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.test.ts +++ b/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.test.ts @@ -10,7 +10,7 @@ import { documentRopstenValidWithDocumentStore as v2documentRopstenValidWithDocu import { documentRopstenMixedIssuance } from "../../../../test/fixtures/v2/documentRopstenMixedIssuance"; import { verificationBuilder } from "../../verificationBuilder"; -const verify = verificationBuilder([openAttestationEthereumDocumentStoreStatus]); +const verify = verificationBuilder([openAttestationEthereumDocumentStoreStatus], { network: "ropsten" }); describe("OpenAttestationEthereumDocumentStoreStatus", () => { describe("v2", () => { @@ -18,10 +18,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { const fragment = await verify( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - { ...v2documentRopstenValidWithDocumentStore, data: null }, - { - network: "ropsten", - } + { ...v2documentRopstenValidWithDocumentStore, data: null } ); expect(fragment).toStrictEqual([ { @@ -38,17 +35,12 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { ]); }); it("should return a skipped fragment when document does not have issuers", async () => { - const fragment = await verify( - { - ...v2documentRopstenValidWithDocumentStore, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - data: { ...v2documentRopstenValidWithDocumentStore.data, issuers: null }, - }, - { - network: "ropsten", - } - ); + const fragment = await verify({ + ...v2documentRopstenValidWithDocumentStore, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + data: { ...v2documentRopstenValidWithDocumentStore.data, issuers: null }, + }); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumDocumentStoreStatus", @@ -64,9 +56,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { ]); }); it("should return a skipped fragment when document uses token registry", async () => { - const fragment = await verify(documentRopstenNotIssuedWithTokenRegistry, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenNotIssuedWithTokenRegistry); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumDocumentStoreStatus", @@ -82,23 +72,18 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { ]); }); it("should return an invalid fragment when document store is invalid", async () => { - const fragment = await verify( - { - ...documentRopstenRevokedWithDocumentStore, - data: { - ...documentRopstenRevokedWithDocumentStore.data, - issuers: [ - { - ...documentRopstenRevokedWithDocumentStore.data.issuers[0], - documentStore: "0c837c55-4948-4a5a-9ed3-801889db9ce3:string:0xabcd", - }, - ], - }, + const fragment = await verify({ + ...documentRopstenRevokedWithDocumentStore, + data: { + ...documentRopstenRevokedWithDocumentStore.data, + issuers: [ + { + ...documentRopstenRevokedWithDocumentStore.data.issuers[0], + documentStore: "0c837c55-4948-4a5a-9ed3-801889db9ce3:string:0xabcd", + }, + ], }, - { - network: "ropsten", - } - ); + }); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -131,23 +116,18 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { `); }); it("should return an invalid fragment when document store does not exists", async () => { - const fragment = await verify( - { - ...documentRopstenRevokedWithDocumentStore, - data: { - ...documentRopstenRevokedWithDocumentStore.data, - issuers: [ - { - ...documentRopstenRevokedWithDocumentStore.data.issuers[0], - documentStore: "0c837c55-4948-4a5a-9ed3-801889db9ce3:string:0x0000000000000000000000000000000000000000", - }, - ], - }, + const fragment = await verify({ + ...documentRopstenRevokedWithDocumentStore, + data: { + ...documentRopstenRevokedWithDocumentStore.data, + issuers: [ + { + ...documentRopstenRevokedWithDocumentStore.data.issuers[0], + documentStore: "0c837c55-4948-4a5a-9ed3-801889db9ce3:string:0x0000000000000000000000000000000000000000", + }, + ], }, - { - network: "ropsten", - } - ); + }); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -180,9 +160,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { `); }); it("should return an invalid fragment when document was not issued", async () => { - const fragment = await verify(documentRopstenNotIssuedWithCertificateStore, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenNotIssuedWithCertificateStore); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -215,9 +193,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { `); }); it("should return an invalid fragment when document with document store that has been revoked", async () => { - const fragment = await verify(documentRopstenRevokedWithDocumentStore, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenRevokedWithDocumentStore); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -257,9 +233,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { `); }); it("should return an invalid fragment when document with certificate store that has been revoked", async () => { - const fragment = await verify(documentRopstenRevokedWithCertificateStore, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenRevokedWithCertificateStore); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -299,9 +273,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { `); }); it("should return a valid fragment when document with document store that has not been revoked", async () => { - const fragment = await verify(v2documentRopstenValidWithDocumentStore, { - network: "ropsten", - }); + const fragment = await verify(v2documentRopstenValidWithDocumentStore); expect(fragment).toStrictEqual([ { @@ -330,9 +302,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { ]); }); it("should return a valid fragment when document with certificate store that has not been revoked", async () => { - const fragment = await verify(documentRopstenValidWithCertificateStore, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenValidWithCertificateStore); expect(fragment).toStrictEqual([ { @@ -361,9 +331,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { ]); }); it("should return an invalid fragment when used with other issuance methods", async () => { - const fragment = await verify(documentRopstenMixedIssuance, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenMixedIssuance); expect(fragment).toMatchInlineSnapshot(` Array [ @@ -384,9 +352,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { }); describe("v3", () => { it("should return an invalid fragment when document with document store that has been revoked", async () => { - const fragment = await verify(documentRopstenRevoked, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenRevoked); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -422,9 +388,7 @@ describe("OpenAttestationEthereumDocumentStoreStatus", () => { `); }); it("should return a valid fragment when document with document store that has not been revoked", async () => { - const fragment = await verify(v3documentRopstenValidWithDocumentStore, { - network: "ropsten", - }); + const fragment = await verify(v3documentRopstenValidWithDocumentStore); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumDocumentStoreStatus", diff --git a/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.ts b/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.ts index 7021bccc..e3c7bea8 100644 --- a/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.ts +++ b/src/verifiers/documentStatus/documentStore/ethereumDocumentStoreStatus.ts @@ -1,10 +1,9 @@ import { utils, getData, v2, v3, WrappedDocument } from "@govtechsg/open-attestation"; -import { errors } from "ethers"; +import { errors, providers } from "ethers"; import { DocumentStoreFactory } from "@govtechsg/document-store"; import { DocumentStore } from "@govtechsg/document-store/src/contracts/DocumentStore"; import { Hash, VerificationFragmentType, VerificationFragment, Verifier } from "../../../types/core"; import { OpenAttestationEthereumDocumentStoreStatusCode, Reason } from "../../../types/error"; -import { getProvider } from "../../../common/utils"; import { CodedError } from "../../../common/error"; import { withCodedErrorHandler } from "../../../common/errorHandler"; @@ -110,14 +109,14 @@ export const decodeError = (error: any) => { export const isIssuedOnDocumentStore = async ({ documentStore, merkleRoot, - network, + provider, }: { documentStore: string; merkleRoot: string; - network: string; + provider: providers.Provider; }): Promise => { try { - const documentStoreContract = await DocumentStoreFactory.connect(documentStore, getProvider({ network })); + const documentStoreContract = await DocumentStoreFactory.connect(documentStore, provider); const issued = await documentStoreContract.isIssued(merkleRoot); return issued @@ -177,18 +176,18 @@ export const isAnyHashRevoked = async (smartContract: DocumentStore, intermediat export const isRevokedOnDocumentStore = async ({ documentStore, merkleRoot, - network, + provider, targetHash, proofs, }: { documentStore: string; merkleRoot: string; - network: string; + provider: providers.Provider; targetHash: Hash; proofs?: Hash[]; }): Promise => { try { - const documentStoreContract = await DocumentStoreFactory.connect(documentStore, getProvider({ network })); + const documentStoreContract = await DocumentStoreFactory.connect(documentStore, provider); const intermediateHashes = getIntermediateHashes(targetHash, proofs); const revokedHash = await isAnyHashRevoked(documentStoreContract, intermediateHashes); @@ -265,7 +264,7 @@ export const openAttestationEthereumDocumentStoreStatus: Verifier< const issuanceStatuses: IssuanceStatus[] = await Promise.all( documentStores.map((documentStore) => - isIssuedOnDocumentStore({ documentStore, merkleRoot, network: options.network }) + isIssuedOnDocumentStore({ documentStore, merkleRoot, provider: options.provider }) ) ); const notIssued = issuanceStatuses.find((status): status is InvalidIssuanceStatus => !status.issued); @@ -292,7 +291,7 @@ export const openAttestationEthereumDocumentStoreStatus: Verifier< merkleRoot, targetHash, proofs, - network: options.network, + provider: options.provider, }) ) ); diff --git a/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.test.ts b/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.test.ts index 4496a64e..1c64f129 100644 --- a/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.test.ts +++ b/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.test.ts @@ -8,7 +8,7 @@ import { documentRopstenNotIssuedWithDocumentStore } from "../../../../test/fixt import { documentRopstenMixedIssuance } from "../../../../test/fixtures/v2/documentRopstenMixedIssuance"; import { verificationBuilder } from "../../verificationBuilder"; -const verify = verificationBuilder([openAttestationEthereumTokenRegistryStatus]); +const verify = verificationBuilder([openAttestationEthereumTokenRegistryStatus], { network: "ropsten" }); describe("openAttestationEthereumTokenRegistryStatus", () => { describe("v2", () => { @@ -16,10 +16,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { const fragment = await verify( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - { ...documentRopstenValidWithToken, data: null }, - { - network: "ropsten", - } + { ...documentRopstenValidWithToken, data: null } ); expect(fragment).toStrictEqual([ { @@ -35,17 +32,12 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { ]); }); it("should return a skipped fragment when document does not have issuers", async () => { - const fragment = await verify( - { - ...documentRopstenValidWithToken, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - data: { ...documentRopstenValidWithToken.data, issuers: null }, - }, - { - network: "ropsten", - } - ); + const fragment = await verify({ + ...documentRopstenValidWithToken, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + data: { ...documentRopstenValidWithToken.data, issuers: null }, + }); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumTokenRegistryStatus", @@ -60,9 +52,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { ]); }); it("should return a skipped fragment when document uses certificate store", async () => { - const fragment = await verify(documentRopstenNotIssuedWithCertificateStore, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenNotIssuedWithCertificateStore); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumTokenRegistryStatus", @@ -77,9 +67,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { ]); }); it("should return a skipped fragment when document uses document store", async () => { - const fragment = await verify(documentRopstenNotIssuedWithDocumentStore, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenNotIssuedWithDocumentStore); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumTokenRegistryStatus", @@ -94,23 +82,18 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { ]); }); it("should return an invalid fragment when token registry is invalid", async () => { - const fragment = await verify( - { - ...documentRopstenNotIssuedWithTokenRegistry, - data: { - ...documentRopstenNotIssuedWithTokenRegistry.data, - issuers: [ - { - ...documentRopstenNotIssuedWithTokenRegistry.data.issuers[0], - tokenRegistry: "0fb5b63a-aaa5-4e6e-a6f4-391c0f6ba423:string:0xabcd", - }, - ], - }, + const fragment = await verify({ + ...documentRopstenNotIssuedWithTokenRegistry, + data: { + ...documentRopstenNotIssuedWithTokenRegistry.data, + issuers: [ + { + ...documentRopstenNotIssuedWithTokenRegistry.data.issuers[0], + tokenRegistry: "0fb5b63a-aaa5-4e6e-a6f4-391c0f6ba423:string:0xabcd", + }, + ], }, - { - network: "ropsten", - } - ); + }); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -141,23 +124,18 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { `); }); it("should return an invalid fragment when token registry does not exist", async () => { - const fragment = await verify( - { - ...documentRopstenNotIssuedWithTokenRegistry, - data: { - ...documentRopstenNotIssuedWithTokenRegistry.data, - issuers: [ - { - ...documentRopstenNotIssuedWithTokenRegistry.data.issuers[0], - tokenRegistry: "0fb5b63a-aaa5-4e6e-a6f4-391c0f6ba423:string:0x0000000000000000000000000000000000000000", - }, - ], - }, + const fragment = await verify({ + ...documentRopstenNotIssuedWithTokenRegistry, + data: { + ...documentRopstenNotIssuedWithTokenRegistry.data, + issuers: [ + { + ...documentRopstenNotIssuedWithTokenRegistry.data.issuers[0], + tokenRegistry: "0fb5b63a-aaa5-4e6e-a6f4-391c0f6ba423:string:0x0000000000000000000000000000000000000000", + }, + ], }, - { - network: "ropsten", - } - ); + }); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -188,9 +166,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { `); }); it("should return an invalid fragment when document with token registry has not been minted", async () => { - const fragment = await verify(documentRopstenNotIssuedWithTokenRegistry, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenNotIssuedWithTokenRegistry); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -221,9 +197,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { `); }); it("should return a valid fragment when document with token registry has been minted", async () => { - const fragment = await verify(documentRopstenValidWithToken, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenValidWithToken); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumTokenRegistryStatus", @@ -242,18 +216,13 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { ]); }); it("should return an error fragment when document has 2 issuers with token registry", async () => { - const fragment = await verify( - { - ...documentRopstenValidWithToken, - data: { - ...documentRopstenValidWithToken.data, - issuers: [documentRopstenValidWithToken.data.issuers[0], documentRopstenValidWithToken.data.issuers[0]], - }, + const fragment = await verify({ + ...documentRopstenValidWithToken, + data: { + ...documentRopstenValidWithToken.data, + issuers: [documentRopstenValidWithToken.data.issuers[0], documentRopstenValidWithToken.data.issuers[0]], }, - { - network: "ropsten", - } - ); + }); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -271,24 +240,19 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { `); }); it("should return an error fragment when document uses 2 different verification method", async () => { - const fragment = await verify( - { - ...documentRopstenValidWithToken, - data: { - ...documentRopstenValidWithToken.data, - issuers: [ - documentRopstenValidWithToken.data.issuers[0], - { - identityProof: documentRopstenValidWithToken.data.issuers[0].identityProof, - name: "Second Issuer", - }, - ], - }, + const fragment = await verify({ + ...documentRopstenValidWithToken, + data: { + ...documentRopstenValidWithToken.data, + issuers: [ + documentRopstenValidWithToken.data.issuers[0], + { + identityProof: documentRopstenValidWithToken.data.issuers[0].identityProof, + name: "Second Issuer", + }, + ], }, - { - network: "ropsten", - } - ); + }); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -306,9 +270,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { `); }); it("should return an invalid fragment when used with other issuance methods", async () => { - const fragment = await verify(documentRopstenMixedIssuance, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenMixedIssuance); expect(fragment).toMatchInlineSnapshot(` Array [ @@ -329,9 +291,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { }); describe("v3", () => { it("should return an invalid fragment when document with token registry has not been minted", async () => { - const fragment = await verify(v3documentRopstenNotIssuedWithTokenRegistry, { - network: "ropsten", - }); + const fragment = await verify(v3documentRopstenNotIssuedWithTokenRegistry); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -360,9 +320,7 @@ describe("openAttestationEthereumTokenRegistryStatus", () => { `); }); it("should return a valid fragment when document with document store has been minted", async () => { - const fragment = await verify(v3documentRopstenValidWithTokenRegistry, { - network: "ropsten", - }); + const fragment = await verify(v3documentRopstenValidWithTokenRegistry); expect(fragment).toStrictEqual([ { name: "OpenAttestationEthereumTokenRegistryStatus", diff --git a/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.ts b/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.ts index efad8e49..d85a2fe0 100644 --- a/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.ts +++ b/src/verifiers/documentStatus/tokenRegistry/ethereumTokenRegistryStatus.ts @@ -1,9 +1,8 @@ import { getData, utils, v2, v3, WrappedDocument } from "@govtechsg/open-attestation"; import { TradeTrustErc721Factory } from "@govtechsg/token-registry"; -import { constants, errors } from "ethers"; +import { constants, errors, providers } from "ethers"; import { VerificationFragmentType, Verifier } from "../../../types/core"; import { OpenAttestationEthereumTokenRegistryStatusCode, Reason } from "../../../types/error"; -import { getProvider } from "../../../common/utils"; import { withCodedErrorHandler } from "../../../common/errorHandler"; import { CodedError } from "../../../common/error"; @@ -57,15 +56,24 @@ export const getTokenRegistry = ( } }; +const isNonExistentToken = (error: any) => { + const message: string | undefined = error.body?.error?.message; + if (!message) return false; + return message.includes("owner query for nonexistent token"); +}; +const isMissingTokenRegistry = (error: any) => { + return ( + !error.reason && + error.method?.toLowerCase() === "ownerOf(uint256)".toLowerCase() && + error.code === errors.CALL_EXCEPTION + ); +}; const decodeError = (error: any) => { const reason = error.reason && Array.isArray(error.reason) ? error.reason[0] : error.reason ?? ""; switch (true) { - case reason.toLowerCase() === "ERC721: owner query for nonexistent token".toLowerCase() && - error.code === errors.CALL_EXCEPTION: + case isNonExistentToken(error): return `Document has not been issued under token registry`; - case !error.reason && - error.method?.toLowerCase() === "ownerOf(uint256)".toLowerCase() && - error.code === errors.CALL_EXCEPTION: + case isMissingTokenRegistry(error): return `Token registry is not found`; case reason.toLowerCase() === "ENS name not configured".toLowerCase() && error.code === errors.UNSUPPORTED_OPERATION: @@ -88,14 +96,14 @@ const decodeError = (error: any) => { export const isTokenMintedOnRegistry = async ({ tokenRegistry, merkleRoot, - network, + provider, }: { tokenRegistry: string; merkleRoot: string; - network: string; + provider: providers.Provider; }): Promise => { try { - const tokenRegistryContract = await TradeTrustErc721Factory.connect(tokenRegistry, getProvider({ network })); + const tokenRegistryContract = await TradeTrustErc721Factory.connect(tokenRegistry, provider); const minted = await tokenRegistryContract.ownerOf(merkleRoot).then((owner) => !(owner === constants.AddressZero)); return minted ? { minted, address: tokenRegistry } @@ -157,7 +165,7 @@ export const openAttestationEthereumTokenRegistryStatus: Verifier< async (document, options) => { const tokenRegistry = getTokenRegistry(document); const merkleRoot = `0x${document.signature.merkleRoot}`; - const mintStatus = await isTokenMintedOnRegistry({ tokenRegistry, merkleRoot, network: options.network }); + const mintStatus = await isTokenMintedOnRegistry({ tokenRegistry, merkleRoot, provider: options.provider }); const status: MintedStatus = mintStatus.minted ? { diff --git a/src/verifiers/issuerIdentity/did/didIdentityProof.test.ts b/src/verifiers/issuerIdentity/did/didIdentityProof.test.ts index 7528f489..02814abf 100644 --- a/src/verifiers/issuerIdentity/did/didIdentityProof.test.ts +++ b/src/verifiers/issuerIdentity/did/didIdentityProof.test.ts @@ -4,10 +4,12 @@ import { documentDidSigned } from "../../../../test/fixtures/v2/documentDidSigne import { documentDidWrongSignature } from "../../../../test/fixtures/v2/documentDidWrongSignature"; import { documentDnsDidSigned } from "../../../../test/fixtures/v2/documentDnsDidSigned"; import { documentDidMixedTokenRegistry } from "../../../../test/fixtures/v2/documentDidMixedTokenRegistry"; +import { getProvider } from "../../../common/utils"; -// TODO Temporarily passing in this option, until make the entire option optional in another PR const options = { - network: "ropsten", + provider: getProvider({ + network: "ropsten", + }), }; describe("skip", () => { diff --git a/src/verifiers/issuerIdentity/dnsDid/dnsDidProof.test.ts b/src/verifiers/issuerIdentity/dnsDid/dnsDidProof.test.ts index 8e6b14cf..64025d5a 100644 --- a/src/verifiers/issuerIdentity/dnsDid/dnsDidProof.test.ts +++ b/src/verifiers/issuerIdentity/dnsDid/dnsDidProof.test.ts @@ -7,9 +7,10 @@ import { documentDnsDidMixedTokenRegistryValid, documentDnsDidMixedTokenRegistryInvalid, } from "../../../../test/fixtures/v2/documentDnsDidMixedTokenRegistry"; -// TODO Temporarily passing in this option, until make the entire option optional in another PR +import { getProvider } from "../../../common/utils"; + const options = { - network: "ropsten", + provider: getProvider({ network: "ropsten" }), }; describe("skip", () => { diff --git a/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.ts b/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.ts index 66ccc81d..50df98c9 100644 --- a/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.ts +++ b/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.ts @@ -1,7 +1,6 @@ import { getData, v2, v3, WrappedDocument, utils as oaUtils } from "@govtechsg/open-attestation"; import { getDocumentStoreRecords } from "@govtechsg/dnsprove"; -import { getDefaultProvider } from "ethers"; -import { VerificationFragmentType, VerificationManagerOptions, Verifier } from "../../../types/core"; +import { VerificationFragmentType, VerifierOptions, Verifier } from "../../../types/core"; import { OpenAttestationDnsTxtCode, Reason } from "../../../types/error"; import { withCodedErrorHandler } from "../../../common/errorHandler"; @@ -25,13 +24,13 @@ export type Identity = ValidIdentity | InvalidIdentity; const resolveIssuerIdentity = async ( issuer: v2.Issuer | v3.Issuer, smartContractAddress: string, - options: VerificationManagerOptions + options: VerifierOptions ): Promise => { const type = issuer?.identityProof?.type ?? ""; const location = issuer?.identityProof?.location ?? ""; if (type !== "DNS-TXT") throw new Error("Identity type not supported"); if (!location) throw new Error("Location is missing"); - const network = await getDefaultProvider(options.network).getNetwork(); + const network = await options.provider.getNetwork(); const records = await getDocumentStoreRecords(location); const matchingRecord = records.find( (record) => @@ -65,7 +64,7 @@ const isWrappedV2Document = (document: any): document is WrappedDocument | WrappedDocument, - VerificationManagerOptions, + VerifierOptions, Identity | Identity[] > = { skip: () => { diff --git a/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v2.test.ts b/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v2.test.ts index d14c018e..04ca26a3 100644 --- a/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v2.test.ts +++ b/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v2.test.ts @@ -3,17 +3,14 @@ import { documentRopstenValidWithToken } from "../../../../test/fixtures/v2/docu import { documentRopstenMixedIssuance } from "../../../../test/fixtures/v2/documentRopstenMixedIssuance"; import { verificationBuilder } from "../../verificationBuilder"; -const verify = verificationBuilder([openAttestationDnsTxtIdentityProof]); +const verify = verificationBuilder([openAttestationDnsTxtIdentityProof], { network: "ropsten" }); describe("OpenAttestationDnsTxt v2 document", () => { describe("with one issuer", () => { it("should return a skipped fragment when document does not have data", async () => { const fragment = await verify( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - { ...documentRopstenValidWithToken, data: null }, - { - network: "ropsten", - } + { ...documentRopstenValidWithToken, data: null } ); expect(fragment).toMatchInlineSnapshot(` Array [ @@ -34,10 +31,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { const fragment = await verify( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - { ...documentRopstenValidWithToken, data: { ...documentRopstenValidWithToken.data, issuers: null } }, - { - network: "ropsten", - } + { ...documentRopstenValidWithToken, data: { ...documentRopstenValidWithToken.data, issuers: null } } ); expect(fragment).toMatchInlineSnapshot(` Array [ @@ -55,9 +49,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { `); }); it("should return a valid fragment when document has valid identity", async () => { - const fragment = await verify(documentRopstenValidWithToken, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenValidWithToken); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -93,9 +85,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { }, }; - const fragment = await verify(document, { - network: "ropsten", - }); + const fragment = await verify(document); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -132,9 +122,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { }, }; - const fragment = await verify(document, { - network: "ropsten", - }); + const fragment = await verify(document); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -165,9 +153,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - const fragment = await verify(document, { - network: "ropsten", - }); + const fragment = await verify(document); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -213,9 +199,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore valid error, need to ignore - const fragment = await verify(document, { - network: "ropsten", - }); + const fragment = await verify(document); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -246,11 +230,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "name": "OpenAttestationDnsTxtIdentityProof", @@ -279,11 +259,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "name": "OpenAttestationDnsTxtIdentityProof", @@ -315,11 +291,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "name": "OpenAttestationDnsTxtIdentityProof", @@ -351,11 +323,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "name": "OpenAttestationDnsTxtIdentityProof", @@ -393,11 +361,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "data": Array [ @@ -452,11 +416,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "data": Array [ @@ -503,9 +463,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { }, }; - const fragment = await verify(document, { - network: "ropsten", - }); + const fragment = await verify(document); expect(fragment).toMatchInlineSnapshot(` Array [ Object { @@ -553,11 +511,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { ], }, }; - expect( - await verify(document, { - network: "ropsten", - }) - ).toMatchInlineSnapshot(` + expect(await verify(document)).toMatchInlineSnapshot(` Array [ Object { "name": "OpenAttestationDnsTxtIdentityProof", @@ -573,9 +527,7 @@ describe("OpenAttestationDnsTxt v2 document", () => { `); }); it("should return an invalid fragment when used with other issuance methods", async () => { - const fragment = await verify(documentRopstenMixedIssuance, { - network: "ropsten", - }); + const fragment = await verify(documentRopstenMixedIssuance); expect(fragment).toMatchInlineSnapshot(` Array [ diff --git a/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v3.test.ts b/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v3.test.ts index 164c3e26..12e2e931 100644 --- a/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v3.test.ts +++ b/src/verifiers/issuerIdentity/dnsTxt/openAttestationDnsTxt.v3.test.ts @@ -1,5 +1,9 @@ import { openAttestationDnsTxtIdentityProof } from "./openAttestationDnsTxt"; import { documentRopstenValidWithDocumentStore } from "../../../../test/fixtures/v3/documentRopstenValid"; +import { getProvider } from "../../../common/utils"; + +const provider = getProvider({ network: "ropsten" }); +const options = { provider }; describe("OpenAttestationDnsTxt v3 document", () => { it("should return a valid fragment when document has valid identity", async () => { @@ -17,9 +21,7 @@ describe("OpenAttestationDnsTxt v3 document", () => { }, }, }, - { - network: "ropsten", - } + options ); expect(fragment).toMatchInlineSnapshot(` Object { @@ -32,9 +34,7 @@ describe("OpenAttestationDnsTxt v3 document", () => { `); }); it("should return an invalid fragment when document identity does not match", async () => { - const fragment = await openAttestationDnsTxtIdentityProof.verify(documentRopstenValidWithDocumentStore, { - network: "ropsten", - }); + const fragment = await openAttestationDnsTxtIdentityProof.verify(documentRopstenValidWithDocumentStore, options); expect(fragment).toMatchInlineSnapshot(` Object { "location": "some.io", @@ -66,9 +66,7 @@ describe("OpenAttestationDnsTxt v3 document", () => { }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore valid error, need to ignore - const fragment = await openAttestationDnsTxtIdentityProof.verify(document, { - network: "ropsten", - }); + const fragment = await openAttestationDnsTxtIdentityProof.verify(document, options); expect(fragment).toMatchInlineSnapshot(` Object { "data": [Error: Identity type not supported], @@ -99,9 +97,7 @@ describe("OpenAttestationDnsTxt v3 document", () => { }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore valid error, need to ignore - const fragment = await openAttestationDnsTxtIdentityProof.verify(document, { - network: "ropsten", - }); + const fragment = await openAttestationDnsTxtIdentityProof.verify(document, options); expect(fragment).toMatchInlineSnapshot(` Object { "data": [Error: Location is missing], @@ -119,11 +115,9 @@ describe("OpenAttestationDnsTxt v3 document", () => { describe("test", () => { it("should return true if identityProof type is DNS-TXT", () => { - expect( - openAttestationDnsTxtIdentityProof.test(documentRopstenValidWithDocumentStore, { - network: "ropsten", - }) - ).toStrictEqual(true); + expect(openAttestationDnsTxtIdentityProof.test(documentRopstenValidWithDocumentStore, options)).toStrictEqual( + true + ); }); it("should return false if identityProof type is not DNS-TXT", () => { const document = { @@ -141,7 +135,7 @@ describe("OpenAttestationDnsTxt v3 document", () => { }; expect( openAttestationDnsTxtIdentityProof.test(document, { - network: "ropsten", + provider, }) ).toStrictEqual(false); }); diff --git a/src/verifiers/verificationBuilder.ts b/src/verifiers/verificationBuilder.ts index b5ee8e13..43e6ff87 100644 --- a/src/verifiers/verificationBuilder.ts +++ b/src/verifiers/verificationBuilder.ts @@ -1,21 +1,31 @@ -import { VerificationFragment, VerificationManagerOptions, Verifier } from "../types/core"; +import { + VerificationBuilderOptions, + VerificationFragment, + PromiseCallback, + Verifier, + VerifierOptions, +} from "../types/core"; +import { getProvider } from "../common/utils"; /** * A verification manager will run a list of {@link Verifier} over a signed document. * Before running each verifier, the manager will make sure the verifier can handle the specific document by calling its exposed test function. * The manager will return the consolidated list of {@link VerificationFragment} */ -export const verificationBuilder = (verifiers: Verifier[]) => ( - document: Document, - options: VerificationManagerOptions -): Promise => { +export const verificationBuilder = ( + verifiers: Verifier[], + builderOptions: VerificationBuilderOptions +) => (document: Document, promisesCallback?: PromiseCallback): Promise => { + const verifierOptions: VerifierOptions = { + provider: getProvider(builderOptions), + }; const promises: Promise[] = verifiers.map((verifier) => { - if (verifier.test(document, options)) { - return verifier.verify(document, options); + if (verifier.test(document, verifierOptions)) { + return verifier.verify(document, verifierOptions); } - return verifier.skip(document, options); + return verifier.skip(document, verifierOptions); }); - options.promisesCallback?.(promises); + promisesCallback?.(promises); return Promise.all(promises); }; diff --git a/src/verify.v2.integration.test.ts b/src/verify.v2.integration.test.ts index 0ca55cb5..9dece212 100644 --- a/src/verify.v2.integration.test.ts +++ b/src/verify.v2.integration.test.ts @@ -31,19 +31,21 @@ import { documentDidMissingProof } from "../test/fixtures/v2/documentDidMissingP import { documentMainnetInvalidWithOddLengthMerkleRoot } from "../test/fixtures/v2/documentMainnetInvalidWithOddLengthMerkleRoot"; import { documentMainnetInvalidWithIncorrectMerkleRoot } from "../test/fixtures/v2/documentMainnetInvalidWithIncorrectMerkleRoot"; import { documentRopstenObfuscated } from "../test/fixtures/v2/documentRopstenObfuscated"; +import { INFURA_API_KEY } from "./config"; + +const verifyHomestead = verify; +const verifyRopsten = verificationBuilder(openAttestationVerifiers, { network: "ropsten" }); +const verifyRinkeby = verificationBuilder(openAttestationVerifiers, { network: "rinkeby" }); describe("verify(integration)", () => { afterEach(() => { delete process.env.ETHEREUM_PROVIDER; }); it("should skip all verifiers when the document is an empty object", async () => { - const fragments = await verify( + const fragments = await verifyRopsten( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - {}, - { - network: "ropsten", - } + {} ); expect(fragments).toMatchInlineSnapshot(` Array [ @@ -115,9 +117,7 @@ describe("verify(integration)", () => { expect(isValid(fragments, ["ISSUER_IDENTITY"])).toStrictEqual(false); }); it("should fail for everything when document's hash is invalid and certificate store is invalid", async () => { - const results = await verify(tamperedDocumentWithCertificateStore, { - network: "ropsten", - }); + const results = await verifyHomestead(tamperedDocumentWithCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -204,9 +204,7 @@ describe("verify(integration)", () => { }); it("should fail for OpenAttestationHash and OpenAttestationEthereumDocumentStoreStatus when document's hash is invalid and was not issued", async () => { - const results = await verify(tamperedDocumentWithInvalidCertificateStore, { - network: "ropsten", - }); + const results = await verifyRopsten(tamperedDocumentWithInvalidCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -293,13 +291,10 @@ describe("verify(integration)", () => { }); it("should be valid for all checks for a document with obfuscated fields", async () => { - const fragments = await verify( + const fragments = await verifyRopsten( // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error - documentRopstenObfuscated, - { - network: "ropsten", - } + documentRopstenObfuscated ); expect(fragments).toMatchInlineSnapshot(` Array [ @@ -383,9 +378,7 @@ describe("verify(integration)", () => { }); it("should be valid for all checks when document with certificate store is valid on ropsten", async () => { - const results = await verify(documentRopstenValidWithCertificateStore, { - network: "ropsten", - }); + const results = await verifyRopsten(documentRopstenValidWithCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -465,9 +458,7 @@ describe("verify(integration)", () => { }); it("should be valid for all checks when document with token registry is valid on ropsten", async () => { - const results = await verify(documentRopstenValidWithToken, { - network: "ropsten", - }); + const results = await verifyRopsten(documentRopstenValidWithToken); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -537,9 +528,7 @@ describe("verify(integration)", () => { expect(isValid(results)).toStrictEqual(true); }); it("should be invalid with a merkle root that is odd-length", async () => { - const results = await verify(documentMainnetInvalidWithOddLengthMerkleRoot, { - network: "mainnet", - }); + const results = await verifyHomestead(documentMainnetInvalidWithOddLengthMerkleRoot); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -632,9 +621,7 @@ describe("verify(integration)", () => { it("should be invalid with a merkle root that is of incorrect length", async () => { // incorrect length means even-length, but not 64 characters as required of merkleRoots - const results = await verify(documentMainnetInvalidWithIncorrectMerkleRoot, { - network: "mainnet", - }); + const results = await verifyHomestead(documentMainnetInvalidWithIncorrectMerkleRoot); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -733,16 +720,14 @@ describe("verify(integration)", () => { it("should return SERVER_ERROR when Ethers cannot connect to Infura with a valid certificate (HTTP 429)", async () => { server.use( - rest.post("https://mainnet.infura.io/v3/bb46da3f80e040e8ab73c0a9ff365d18", (req, res, ctx) => { + rest.post(`https://mainnet.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { return res( ctx.status(429, "Mocked rate limit error"), ctx.json({ jsonrpc: "2.0", result: "0xs0meR4nd0mErr0r", id: 1 }) ); }) ); - const results = await verify(documentMainnetValidWithCertificateStore, { - network: "homestead", - }); + const results = await verifyHomestead(documentMainnetValidWithCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -811,16 +796,14 @@ describe("verify(integration)", () => { }); it("should return SERVER_ERROR when Ethers cannot connect to Infura with a valid certificate (HTTP 502)", async () => { server.use( - rest.post("https://mainnet.infura.io/v3/bb46da3f80e040e8ab73c0a9ff365d18", (req, res, ctx) => { + rest.post(`https://mainnet.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { return res( ctx.status(502, "Mocked rate limit error"), ctx.json({ jsonrpc: "2.0", result: "0xs0meR4nd0mErr0r", id: 2 }) ); }) ); - const results = await verify(documentMainnetValidWithCertificateStore, { - network: "homestead", - }); + const results = await verifyHomestead(documentMainnetValidWithCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -890,16 +873,14 @@ describe("verify(integration)", () => { it("should return SERVER_ERROR when Ethers cannot connect to Infura with an invalid certificate (HTTP 429)", async () => { // NOTE: Purpose of this test is to use a mainnet cert on ropsten. The mainnet cert store is perfectly valid, but does not exist on ropsten. server.use( - rest.post("https://ropsten.infura.io/v3/bb46da3f80e040e8ab73c0a9ff365d18", (req, res, ctx) => { + rest.post(`https://ropsten.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { return res( ctx.status(429, "Mocked rate limit error"), ctx.json({ jsonrpc: "2.0", result: "0xs0meR4nd0mErr0r", id: 3 }) ); }) ); - const results = await verify(documentMainnetValidWithCertificateStore, { - network: "ropsten", - }); + const results = await verifyRopsten(documentMainnetValidWithCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -969,16 +950,14 @@ describe("verify(integration)", () => { it("should return SERVER_ERROR when Ethers cannot connect to Infura with an invalid certificate (HTTP 502)", async () => { // NOTE: Purpose of this test is to use a mainnet cert on ropsten. The mainnet cert store is perfectly valid, but does not exist on ropsten. server.use( - rest.post("https://ropsten.infura.io/v3/bb46da3f80e040e8ab73c0a9ff365d18", (req, res, ctx) => { + rest.post(`https://ropsten.infura.io/v3/${INFURA_API_KEY}`, (req, res, ctx) => { return res( ctx.status(502, "Mocked rate limit error"), ctx.json({ jsonrpc: "2.0", result: "0xs0meR4nd0mErr0r", id: 4 }) ); }) ); - const results = await verify(documentMainnetValidWithCertificateStore, { - network: "ropsten", - }); + const results = await verifyRopsten(documentMainnetValidWithCertificateStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1048,9 +1027,7 @@ describe("verify(integration)", () => { }); it("should fail for OpenAttestationEthereumTokenRegistryStatus when document with token registry was not issued ", async () => { - const results = await verify(documentRopstenRevokedWithToken, { - network: "ropsten", - }); + const results = await verifyRopsten(documentRopstenRevokedWithToken); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1132,9 +1109,7 @@ describe("verify(integration)", () => { }); it("should fail for OpenAttestationEthereumDocumentStoreStatus when document was issued then subsequently revoked", async () => { - const results = await verify(documentRopstenRevokedWithDocumentStore, { - network: "ropsten", - }); + const results = await verifyRopsten(documentRopstenRevokedWithDocumentStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1235,9 +1210,7 @@ describe("verify(integration)", () => { }); it("should work when document with document store has been issued to rinkeby network", async () => { - const results = await verify(documentRinkebyValidWithDocumentStore, { - network: "rinkeby", - }); + const results = await verifyRinkeby(documentRinkebyValidWithDocumentStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1317,9 +1290,7 @@ describe("verify(integration)", () => { }); it("should work when document with document store has been issued and revoked to rinkeby network", async () => { - const results = await verify(documentRinkebyRevokedWithDocumentStore, { - network: "rinkeby", - }); + const results = await verifyRinkeby(documentRinkebyRevokedWithDocumentStore); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1412,9 +1383,7 @@ describe("verify(integration)", () => { }); it("should fail with document signed directly with DID (default verifier)", async () => { - const results = await verify(documentDidSigned, { - network: "rinkeby", - }); + const results = await verifyRinkeby(documentDidSigned); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1489,10 +1458,10 @@ describe("verify(integration)", () => { }); it("should pass with document signed directly with DID with custom verifier", async () => { - const customVerify = verificationBuilder([...openAttestationVerifiers, openAttestationDidIdentityProof]); - const results = await customVerify(documentDidSigned, { + const customVerify = verificationBuilder([...openAttestationVerifiers, openAttestationDidIdentityProof], { network: "rinkeby", }); + const results = await customVerify(documentDidSigned); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1578,9 +1547,7 @@ describe("verify(integration)", () => { }); it("should pass with document signed directly with DID and have top level identity as DNS", async () => { - const results = await verify(documentDnsDidSigned, { - network: "rinkeby", - }); + const results = await verifyRinkeby(documentDnsDidSigned); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1657,9 +1624,7 @@ describe("verify(integration)", () => { }); it("should fail with document incorrectly signed with DID", async () => { - const results = await verify(documentDidWrongSignature, { - network: "rinkeby", - }); + const results = await verifyRinkeby(documentDidWrongSignature); expect(results).toMatchInlineSnapshot(` Array [ Object { @@ -1739,9 +1704,7 @@ describe("verify(integration)", () => { }); it("should fail with document with missing DID signature", async () => { - const results = await verify(documentDidMissingProof, { - network: "rinkeby", - }); + const results = await verifyRinkeby(documentDidMissingProof); expect(results).toMatchInlineSnapshot(` Array [ Object { diff --git a/src/verify.v3.integration.test.ts b/src/verify.v3.integration.test.ts index 443c1bf1..9fc6d8be 100644 --- a/src/verify.v3.integration.test.ts +++ b/src/verify.v3.integration.test.ts @@ -1,4 +1,4 @@ -import { isValid, verify } from "./index"; +import { isValid, verificationBuilder, openAttestationVerifiers } from "./index"; import { documentRopstenValidWithDocumentStore, documentRopstenValidWithTokenRegistry, @@ -7,9 +7,11 @@ import { documentRopstenTampered } from "../test/fixtures/v3/documentRopstenTamp import { documentRopstenNotIssued } from "../test/fixtures/v3/documentRopstenNotIssued"; import { documentRopstenRevoked } from "../test/fixtures/v3/documentRopstenRevoked"; +const verify = verificationBuilder(openAttestationVerifiers, { network: "ropsten" }); + describe("verify v3(integration)", () => { it("should fail for OpenAttestationHash when document's hash is invalid and OpenAttestationDnsTxt when identity is invalid", async () => { - const results = await verify(documentRopstenTampered, { network: "ropsten" }); + const results = await verify(documentRopstenTampered); expect(results).toMatchInlineSnapshot(` Array [ @@ -91,7 +93,7 @@ describe("verify v3(integration)", () => { expect(isValid(results, ["DOCUMENT_INTEGRITY", "DOCUMENT_STATUS"])).toStrictEqual(false); }); it("should fail for OpenAttestationEthereumDocumentStoreStatus when document was not issued and OpenAttestationDnsTxt when identity is invalid", async () => { - const results = await verify(documentRopstenNotIssued, { network: "ropsten" }); + const results = await verify(documentRopstenNotIssued); expect(results).toMatchInlineSnapshot(` Array [ @@ -174,7 +176,7 @@ describe("verify v3(integration)", () => { }); it("should fail for OpenAttestationEthereumDocumentStoreStatus when document was issued an revoked and OpenAttestationDnsTxt when identity is invalid", async () => { - const results = await verify(documentRopstenRevoked, { network: "ropsten" }); + const results = await verify(documentRopstenRevoked); expect(results).toMatchInlineSnapshot(` Array [ @@ -262,7 +264,7 @@ describe("verify v3(integration)", () => { }); it("should fail for OpenAttestationDnsTxt when identity is invalid and be valid for remaining checks when document with certificate store is valid on ropsten", async () => { - const results = await verify(documentRopstenValidWithDocumentStore, { network: "ropsten" }); + const results = await verify(documentRopstenValidWithDocumentStore); expect(results).toMatchInlineSnapshot(` Array [ @@ -341,7 +343,7 @@ describe("verify v3(integration)", () => { }); it("should fail for OpenAttestationDnsTxt when identity is invalid and be valid for remaining checks when document with token registry is valid on ropsten", async () => { - const results = await verify(documentRopstenValidWithTokenRegistry, { network: "ropsten" }); + const results = await verify(documentRopstenValidWithTokenRegistry); expect(results).toMatchInlineSnapshot(` Array [