Skip to content

Commit

Permalink
feat(app, iam, platforms): adds proof of humanity abstraction (passpo…
Browse files Browse the repository at this point in the history
  • Loading branch information
farque65 authored Nov 3, 2022
1 parent 187edf8 commit bf0c860
Show file tree
Hide file tree
Showing 13 changed files with 219 additions and 5 deletions.
6 changes: 3 additions & 3 deletions app/components/CardListV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PlatformGroupSpec, STAMP_PROVIDERS, UpdatedPlatforms } from "../config/

// Providers

import { Twitter, Ens, Lens, Github, Gitcoin } from "@gitcoin/passport-platforms";
import { Twitter, Ens, Lens, Github, Gitcoin, Poh } from "@gitcoin/passport-platforms";

// --- Components
import { LoadingCard } from "./LoadingCard";
Expand Down Expand Up @@ -136,8 +136,8 @@ export const CardList = ({ isLoading = false }: CardListProps): JSX.Element => {
// return <EnsPlatform />;
// case "Brightid":
// return <BrightidPlatform />;
// case "Poh":
// return <PohPlatform />;
case "Poh":
return <GenericEVMPlatform platform={new Poh.PohPlatform()} platFormGroupSpec={Poh.PohProviderConfig} />;
// case "GTC":
// return <GtcPlatform />;
// case "GtcStaking":
Expand Down
4 changes: 4 additions & 0 deletions app/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ module.exports = {
};
},
reactStrictMode: true,
webpack: function (config, options) {
config.experiments = { asyncWebAssembly: true };
return config;
},
};
5 changes: 3 additions & 2 deletions iam/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ import { EnsProvider } from "@gitcoin/passport-platforms/dist/commonjs/src/Ens/P
import { Github } from "@gitcoin/passport-platforms";
import { Gitcoin } from "@gitcoin/passport-platforms";
import { Lens } from "@gitcoin/passport-platforms";
import { Poh } from "@gitcoin/passport-platforms";

import { PohProvider } from "./providers/poh";
// import { PohProvider } from "./providers/poh";
import { POAPProvider } from "./providers/poap";
import { FacebookProvider } from "./providers/facebook";
import { FacebookFriendsProvider } from "./providers/facebookFriends";
Expand Down Expand Up @@ -106,7 +107,7 @@ export const providers = new Providers([
new GoogleProvider(),
new TwitterAuthProvider(),
new EnsProvider(),
new PohProvider(),
new Poh.PohProvider(),
new POAPProvider(),
new FacebookProvider(),
new FacebookFriendsProvider(),
Expand Down
1 change: 1 addition & 0 deletions platforms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@ethersproject/base64": "^5.6.1",
"@ethersproject/providers": "^5.6.2",
"@gitcoin/passport-types": "^1.0.0",
"@spruceid/didkit-wasm": "^0.3.0-alpha0",
"axios": "^0.26.1",
"typescript": "~4.6.3"
},
Expand Down
12 changes: 12 additions & 0 deletions platforms/src/Poh/App-Bindings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* eslint-disable */
import { Platform, PlatformOptions } from "../types";

export class PohPlatform implements Platform {
getOAuthUrl(state: string): Promise<string> {
throw new Error("Method not implemented.");
}
platformId = "Poh";
path = "Poh";
clientId: string = null;
redirectUri: string = null;
}
17 changes: 17 additions & 0 deletions platforms/src/Poh/Providers-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PlatformSpec, PlatformGroupSpec } from "../types";

export const PohPlatformDetails: PlatformSpec = {
icon: "./assets/pohStampIcon.svg",
platform: "Poh",
name: "Proof of Humanity",
description: "Connect your wallet to start the process of verifying with Proof of Humanity.",
connectMessage: "Connect Account",
isEVM: true,
};

export const PohProviderConfig: PlatformGroupSpec[] = [
{
platformGroup: "Account Name",
providers: [{ title: "Encrypted", name: "Poh" }],
},
];
76 changes: 76 additions & 0 deletions platforms/src/Poh/Providers/poh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// ----- Types
import type { Provider, ProviderOptions } from "../../types";
import type { RequestPayload, VerifiedPayload } from "@gitcoin/passport-types";

// ----- Ethers library
import { Contract } from "ethers";
import { StaticJsonRpcProvider } from "@ethersproject/providers";

// ----- Credential verification
import { getAddress } from "../../utils/signer";

// Proof of humanity contract address
const POH_CONTRACT_ADDRESS = "0xC5E9dDebb09Cd64DfaCab4011A0D5cEDaf7c9BDb";

// Proof of humanity Contract ABI
const POH_ABI = [
{
constant: true,
inputs: [{ internalType: "address", name: "_submissionID", type: "address" }],
name: "isRegistered",
outputs: [{ internalType: "bool", name: "", type: "bool" }],
payable: false,
stateMutability: "view",
type: "function",
},
];

// set the network rpc url based on env
export const RPC_URL = process.env.RPC_URL;

// Export a Poh Provider to carry out Proof of Humanity account is registered and active check and return a record object
export class PohProvider implements Provider {
// Give the provider a type so that we can select it from a payload
type = "Poh";
// Options can be set here and/or via the constructor
_options = {};

// construct the provider instance with supplied options
constructor(options: ProviderOptions = {}) {
this._options = { ...this._options, ...options };
}

// Verify that the address defined in the payload exists in the POH register
async verify(payload: RequestPayload): Promise<VerifiedPayload> {
// if a signer is provider we will use that address to verify against
const address = await getAddress(payload);

// attempt to verify POH...
try {
// define a provider using the rpc url
const provider: StaticJsonRpcProvider = new StaticJsonRpcProvider(RPC_URL);

// load Proof of humanity contract
const readContract = new Contract(POH_CONTRACT_ADDRESS, POH_ABI, provider);

// Checks to see if the address is registered with proof of humanity
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
const valid: boolean = await readContract.isRegistered(address);

return {
valid,
record: valid
? {
// store the address into the proof records
address,
}
: undefined,
};
} catch (e) {
return {
valid: false,
error: [JSON.stringify(e)],
};
}
}
}
Empty file added platforms/src/Poh/Shared.ts
Empty file.
70 changes: 70 additions & 0 deletions platforms/src/Poh/__tests__/poh.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// ---- Test subject
import { RequestPayload } from "@gitcoin/passport-types";
import { PohProvider } from "../Providers/poh";

const mockIsRegistered = jest.fn();

jest.mock("ethers", () => {
return {
Contract: jest.fn().mockImplementation(() => {
return {
isRegistered: mockIsRegistered,
};
}),
};
});

const MOCK_ADDRESS = "0x738488886dd94725864ae38252a90be1ab7609c7";

describe("Attempt verification", function () {
beforeEach(() => {
jest.clearAllMocks();
});

it("should return true for an address registered with proof of humanity", async () => {
mockIsRegistered.mockResolvedValueOnce(true);
const poh = new PohProvider();
const verifiedPayload = await poh.verify({
address: MOCK_ADDRESS,
} as RequestPayload);

expect(mockIsRegistered).toBeCalledWith(MOCK_ADDRESS);
expect(verifiedPayload).toEqual({
valid: true,
record: {
address: MOCK_ADDRESS,
},
});
});

it("should return false for an address not registered with proof of humanity", async () => {
mockIsRegistered.mockResolvedValueOnce(false);
const UNREGISTERED_ADDRESS = "0xUNREGISTERED";

const poh = new PohProvider();
const verifiedPayload = await poh.verify({
address: UNREGISTERED_ADDRESS,
} as RequestPayload);

expect(mockIsRegistered).toBeCalledWith(UNREGISTERED_ADDRESS);
expect(verifiedPayload).toEqual({
valid: false,
});
});

it("should return error response when isRegistered call errors", async () => {
mockIsRegistered.mockRejectedValueOnce("some error");
const UNREGISTERED_ADDRESS = "0xUNREGISTERED";

const poh = new PohProvider();
const verifiedPayload = await poh.verify({
address: UNREGISTERED_ADDRESS,
} as RequestPayload);

expect(mockIsRegistered).toBeCalledWith(UNREGISTERED_ADDRESS);
expect(verifiedPayload).toEqual({
valid: false,
error: [JSON.stringify("some error")],
});
});
});
3 changes: 3 additions & 0 deletions platforms/src/Poh/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { PohPlatform } from "./App-Bindings";
export { PohPlatformDetails, PohProviderConfig } from "./Providers-config";
export { PohProvider } from "./Providers/poh";
1 change: 1 addition & 0 deletions platforms/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * as Ens from "./Ens";
export * as Github from "./Github";
export * as Gitcoin from "./Gitcoin";
export * as Lens from "./Lens";
export * as Poh from "./Poh";
24 changes: 24 additions & 0 deletions platforms/src/utils/signer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
// ----- Types
import type { RequestPayload } from "@gitcoin/passport-types";

// ----- Credential verification
import * as DIDKit from "@spruceid/didkit-wasm";
import { verifyCredential } from "@gitcoin/passport-identity/dist/commonjs/src/credentials";

// ----- Verify signed message with ethers
import { utils } from "ethers";

// ----- Verify signed message with ethers
import { JsonRpcProvider, JsonRpcSigner, StaticJsonRpcProvider } from "@ethersproject/providers";

Expand All @@ -21,3 +28,20 @@ export const getRPCProvider = (payload: RequestPayload): JsonRpcSigner | JsonRpc
const provider: StaticJsonRpcProvider = new StaticJsonRpcProvider(rpcUrl);
return provider;
};

// get the address associated with the signer in the payload
export const getAddress = async ({ address, signer, issuer }: RequestPayload): Promise<string> => {
// if signer proof is provided, check validity and return associated address instead of controller
if (signer && signer.challenge && signer.signature) {
// test the provided credential has not been tampered with
const verified = await verifyCredential(DIDKit, signer.challenge);
// check the credential was issued by us for this user...
if (verified && issuer === signer.challenge.issuer && address === signer.challenge.credentialSubject.address) {
// which ever wallet signed this message is the wallet we want to use in provider verifications
return utils.getAddress(utils.verifyMessage(signer.challenge.credentialSubject.challenge, signer.signature));
}
}

// proof was missing/invalid return controller address from the payload
return address;
};
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4762,6 +4762,11 @@
resolved "https://registry.yarnpkg.com/@spruceid/didkit-wasm-node/-/didkit-wasm-node-0.2.1.tgz#85f7023979b4ef84bd6de16c79c67b6e9a08e1db"
integrity sha512-c8e3u5FIRS/2Gf6UHnRPnfRozgKgby4avZzlvIiaJRDVLl1LaX1SE13vEvKV2rAq6NMfZrV4YG908M4uVlT90Q==

"@spruceid/didkit-wasm@^0.3.0-alpha0":
version "0.3.0-rc.0"
resolved "https://registry.yarnpkg.com/@spruceid/didkit-wasm/-/didkit-wasm-0.3.0-rc.0.tgz#2f1e8433506b86457fcfce9595af2c71b6c23cc7"
integrity sha512-LYb+ROUnpLaBUUfCP3ocF9jCFb03E1xTo0hkhVxVvgMuQKsfP4Pg5UzRynXnxGDLTBSeCMsIw3+J7JioLjzW6A==

"@stablelib/aead@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3"
Expand Down

0 comments on commit bf0c860

Please sign in to comment.