Skip to content

Commit

Permalink
feat: verificationBuilder with custom provider (#141)
Browse files Browse the repository at this point in the history
Co-authored-by: Raymond Yeh <ray@geek.sg>

BREAKING CHANGE: Change in API for verify and verification builder. Native cloudflare removed in favor for custom providers.
  • Loading branch information
yehjxraymond authored Nov 19, 2020
1 parent fcbe8f9 commit 1de27f3
Show file tree
Hide file tree
Showing 19 changed files with 288 additions and 398 deletions.
22 changes: 20 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
Expand Down Expand Up @@ -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
Expand Down
14 changes: 9 additions & 5 deletions src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -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);
};
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ const openAttestationVerifiers: Verifiers[] = [
openAttestationDnsDidIdentityProof,
];

const defaultBuilderOption = {
network: "homestead",
};

const verify = verificationBuilder<
| SignedWrappedDocument<v2.OpenAttestationDocument>
| WrappedDocument<v2.OpenAttestationDocument>
| WrappedDocument<v3.OpenAttestationDocument>
>(openAttestationVerifiers);
>(openAttestationVerifiers, defaultBuilderOption);

export * from "./types/core";
export * from "./types/error";
Expand Down
21 changes: 16 additions & 5 deletions src/types/core.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import { SignedWrappedDocument, v2, v3, WrappedDocument } from "@govtechsg/open-attestation";
import { providers } from "ethers";
import { Reason } from "./error";

type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};

/**
* - 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<VerificationFragment>[]) => void;

export interface VerificationBuilderOptionsWithProvider {
provider: providers.Provider;
}

export interface VerificationBuilderOptionsWithNetwork {
network: string;
promisesCallback?: (promises: Promise<VerificationFragment>[]) => void;
}

export type VerificationBuilderOptions = VerificationBuilderOptionsWithProvider | VerificationBuilderOptionsWithNetwork;

export interface VerifierOptions {
provider: providers.Provider;
}

/**
Expand Down Expand Up @@ -54,7 +65,7 @@ interface SkippedVerificationFragment extends VerificationFragment {
}
export interface Verifier<
Document = WrappedDocument<v3.OpenAttestationDocument> | WrappedDocument<v2.OpenAttestationDocument>,
Options = VerificationManagerOptions,
Options = VerifierOptions,
Data = any
> {
skip: (document: Document, options: Options) => Promise<SkippedVerificationFragment>;
Expand Down
18 changes: 6 additions & 12 deletions src/verifiers/documentIntegrity/hash/openAttestationHash.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand All @@ -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", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ const verify: VerifierType["verify"] = withCodedErrorHandler(
"UNSIGNED"
);
const signatureVerificationDeferred: Promise<DidVerificationStatus>[] = 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
Expand Down
Loading

0 comments on commit 1de27f3

Please sign in to comment.