diff --git a/iam/__tests__/additional_signer.test.ts b/iam/__tests__/additional_signer.test.ts index b798b9e1b0..921282392b 100644 --- a/iam/__tests__/additional_signer.test.ts +++ b/iam/__tests__/additional_signer.test.ts @@ -2,11 +2,22 @@ import request from "supertest"; // ---- Test subject -import { app } from "../src/index"; -import { getEip712Issuer } from "../src/issuers"; +import { app } from "../src/index.js"; +import { getEip712Issuer } from "../src/issuers.js"; const issuer = getEip712Issuer(); +jest.mock("../src/utils/oprf", () => ({ + recordToNullifier: async ({ record }: any) => { + const crypto = await import("crypto"); + const hash = crypto.createHash("sha256"); + + hash.update(JSON.stringify(record)); + + return hash.digest("hex"); + }, +})); + jest.mock("../src/utils/bans", () => ({ checkCredentialBans: jest.fn().mockImplementation((input) => Promise.resolve(input)), })); diff --git a/iam/__tests__/autoVerification.test.ts b/iam/__tests__/autoVerification.test.ts index 2544253742..fa90c09f44 100644 --- a/iam/__tests__/autoVerification.test.ts +++ b/iam/__tests__/autoVerification.test.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; import { isAddress } from "ethers"; import axios from "axios"; -import { autoVerificationHandler } from "../src/utils/autoVerification"; +import { autoVerificationHandler } from "../src/utils/autoVerification.js"; import { ApiError } from "../src/utils/helpers.js"; import { checkConditionsAndIssueCredentials } from "../src/utils/credentials.js"; import { VerifiableCredential } from "@gitcoin/passport-types"; diff --git a/iam/__tests__/challenge.test.ts b/iam/__tests__/challenge.test.ts index 4f1d70a9dc..34636d26da 100644 --- a/iam/__tests__/challenge.test.ts +++ b/iam/__tests__/challenge.test.ts @@ -1,5 +1,5 @@ import { RequestPayload } from "@gitcoin/passport-types"; -import { getChallenge } from "../src/utils/challenge"; +import { getChallenge } from "../src/utils/challenge.js"; describe("getChallenge", () => { it("returns a challenge for SignerChallenge", () => { diff --git a/iam/__tests__/credential_issuance.test.ts b/iam/__tests__/credential_issuance.test.ts index 1b8aeecb92..f51341df73 100644 --- a/iam/__tests__/credential_issuance.test.ts +++ b/iam/__tests__/credential_issuance.test.ts @@ -1,7 +1,7 @@ import axios from "axios"; -import { checkCredentialBans } from "../src/utils/bans"; +import { checkCredentialBans } from "../src/utils/bans.js"; import { ErrorResponseBody } from "@gitcoin/passport-types"; -import { ApiError } from "../src/utils/helpers"; +import { ApiError } from "../src/utils/helpers.js"; jest.mock("axios"); const mockedAxios = axios as jest.Mocked; diff --git a/iam/__tests__/easFees.test.ts b/iam/__tests__/easFees.test.ts index f95112a146..71f570db98 100644 --- a/iam/__tests__/easFees.test.ts +++ b/iam/__tests__/easFees.test.ts @@ -1,4 +1,4 @@ -import { getEASFeeAmount } from "../src/utils/easFees"; +import { getEASFeeAmount } from "../src/utils/easFees.js"; import { parseEther } from "ethers"; import Moralis from "moralis"; import { PassportCache } from "@gitcoin/passport-platforms"; diff --git a/iam/__tests__/easPassportSchema.test.ts b/iam/__tests__/easPassportSchema.test.ts index e628ca728c..3dec410cf1 100644 --- a/iam/__tests__/easPassportSchema.test.ts +++ b/iam/__tests__/easPassportSchema.test.ts @@ -1,5 +1,5 @@ -import * as easPassportModule from "../src/utils/easPassportSchema"; -import * as easStampModule from "../src/utils/easStampSchema"; +import * as easPassportModule from "../src/utils/easPassportSchema.js"; +import * as easStampModule from "../src/utils/easStampSchema.js"; import onchainInfo from "../../deployments/onchainInfo.json"; import { VerifiableCredential } from "@gitcoin/passport-types"; diff --git a/iam/__tests__/easStampSchema.test.ts b/iam/__tests__/easStampSchema.test.ts index 429ab75292..46a58c3bf1 100644 --- a/iam/__tests__/easStampSchema.test.ts +++ b/iam/__tests__/easStampSchema.test.ts @@ -1,4 +1,4 @@ -import * as easStampModule from "../src/utils/easStampSchema"; +import * as easStampModule from "../src/utils/easStampSchema.js"; import { VerifiableCredential } from "@gitcoin/passport-types"; import { NO_EXPIRATION, ZERO_BYTES32 } from "@ethereum-attestation-service/eas-sdk"; import { SchemaEncoder } from "@ethereum-attestation-service/eas-sdk"; diff --git a/iam/__tests__/index.test.ts b/iam/__tests__/index.test.ts index 6c0a0c5c24..3a58a5f15b 100644 --- a/iam/__tests__/index.test.ts +++ b/iam/__tests__/index.test.ts @@ -4,8 +4,8 @@ import * as DIDKit from "@spruceid/didkit-wasm-node"; import { PassportCache, providers } from "@gitcoin/passport-platforms"; // ---- Test subject -import { app } from "../src/index"; -import { getAttestationDomainSeparator } from "../src/utils/attestations"; +import { app } from "../src/index.js"; +import { getAttestationDomainSeparator } from "../src/utils/attestations.js"; // ---- Types import { @@ -21,17 +21,28 @@ import { import { MultiAttestationRequest, ZERO_BYTES32, NO_EXPIRATION } from "@ethereum-attestation-service/eas-sdk"; import { parseEther } from "ethers"; -import * as easFeesMock from "../src/utils/easFees"; +import * as easFeesMock from "../src/utils/easFees.js"; import * as identityMock from "@gitcoin/passport-identity"; -import * as easSchemaMock from "../src/utils/easStampSchema"; -import * as easPassportSchemaMock from "../src/utils/easPassportSchema"; -import { IAMError } from "../src/utils/scorerService"; -import { VerifyDidChallengeBaseError, verifyDidChallenge } from "../src/utils/verifyDidChallenge"; -import { getEip712Issuer } from "../src/issuers"; -import { toJsonObject } from "../src/utils/json"; +import * as easSchemaMock from "../src/utils/easStampSchema.js"; +import * as easPassportSchemaMock from "../src/utils/easPassportSchema.js"; +import { IAMError } from "../src/utils/scorerService.js"; +import { VerifyDidChallengeBaseError, verifyDidChallenge } from "../src/utils/verifyDidChallenge.js"; +import { getEip712Issuer } from "../src/issuers.js"; +import { toJsonObject } from "../src/utils/json.js"; const issuer = getEip712Issuer(); +jest.mock("../src/utils/oprf", () => ({ + recordToNullifier: async ({ record }: any) => { + const crypto = await import("crypto"); + const hash = crypto.createHash("sha256"); + + hash.update(JSON.stringify(record)); + + return hash.digest("hex"); + }, +})); + jest.mock("../src/utils/bans", () => ({ checkCredentialBans: jest.fn().mockImplementation((input) => Promise.resolve(input)), })); @@ -831,12 +842,17 @@ describe("POST /verify", function () { jest.spyOn(identityMock, "verifyCredential").mockResolvedValue(true); // create a req against the express app - await request(app) + const response = await request(app) .post("/api/v0.0.0/verify") .send({ challenge, payload }) .set("Accept", "application/json") .expect(200) .expect("Content-Type", /json/); + + response.body.forEach((item: any) => { + expect(item).toHaveProperty("record"); + expect(item.record).toMatchObject({ type: "Simple" }); + }); }); it("should not issue credential for additional signer when invalid address is provided", async () => { (identityMock.verifyCredential as jest.Mock).mockResolvedValueOnce(true); diff --git a/iam/__tests__/index_eas_score.test.ts b/iam/__tests__/index_eas_score.test.ts index 016db6e5a7..a41adcf3d5 100644 --- a/iam/__tests__/index_eas_score.test.ts +++ b/iam/__tests__/index_eas_score.test.ts @@ -3,15 +3,15 @@ import request from "supertest"; import { PassportCache } from "@gitcoin/passport-platforms"; // ---- Test subject -import { app } from "../src/index"; +import { app } from "../src/index.js"; import { MultiAttestationRequest, ZERO_BYTES32, NO_EXPIRATION } from "@ethereum-attestation-service/eas-sdk"; import * as identityMock from "@gitcoin/passport-identity"; -import * as easSchemaMock from "../src/utils/easStampSchema"; -import * as easPassportSchemaMock from "../src/utils/easPassportSchema"; -import { IAMError } from "../src/utils/scorerService"; -import { serializeJson, toJsonObject } from "../src/utils/json"; +import * as easSchemaMock from "../src/utils/easStampSchema.js"; +import * as easPassportSchemaMock from "../src/utils/easPassportSchema.js"; +import { IAMError } from "../src/utils/scorerService.js"; +import { serializeJson, toJsonObject } from "../src/utils/json.js"; jest.mock("@gitcoin/passport-identity", () => ({ ...jest.requireActual("@gitcoin/passport-identity"), diff --git a/iam/__tests__/scorerService.test.ts b/iam/__tests__/scorerService.test.ts index a0d3eb4178..6c73f102cc 100644 --- a/iam/__tests__/scorerService.test.ts +++ b/iam/__tests__/scorerService.test.ts @@ -1,7 +1,7 @@ -import { fetchPassportScore } from "../src/utils/scorerService"; +import { fetchPassportScore } from "../src/utils/scorerService.js"; // Import the entire module to help with typing -import * as scorerService from "../src/utils/scorerService"; +import * as scorerService from "../src/utils/scorerService.js"; // Type for the mocked function (adjust as needed based on the actual implementation) type MockedFunction = jest.MockedFunction; diff --git a/iam/__tests__/scrollDevBadge.test.ts b/iam/__tests__/scrollDevBadge.test.ts index d5062a64c7..ab8be30ebd 100644 --- a/iam/__tests__/scrollDevBadge.test.ts +++ b/iam/__tests__/scrollDevBadge.test.ts @@ -1,9 +1,9 @@ import { Request, Response } from "express"; import { Signature, Contract } from "ethers"; -import { scrollDevBadgeHandler, getScrollRpcUrl } from "../src/utils/scrollDevBadge"; -import { getAttestationSignerForChain } from "../src/utils/attestations"; -import { hasValidIssuer } from "../src/issuers"; -import { getEASFeeAmount } from "../src/utils/easFees"; +import { scrollDevBadgeHandler, getScrollRpcUrl } from "../src/utils/scrollDevBadge.js"; +import { getAttestationSignerForChain } from "../src/utils/attestations.js"; +import { hasValidIssuer } from "../src/issuers.js"; +import { getEASFeeAmount } from "../src/utils/easFees.js"; // Mock external dependencies jest.mock("@spruceid/didkit-wasm-node"); diff --git a/iam/jest.setup.cjs b/iam/jest.setup.cjs index 7362c3139a..585031153f 100644 --- a/iam/jest.setup.cjs +++ b/iam/jest.setup.cjs @@ -18,3 +18,6 @@ process.env.SCROLL_BADGE_PROVIDER_INFO = '{"DeveloperList#PassportCommiterLevel1#6a51c84c":{"contractAddress":"0x71A848A38fFCcA5c7A431F2BB411Ab632Fa0c456","level":1}}'; process.env.SCROLL_BADGE_ATTESTATION_SCHEMA_UID = "0xa35b5470ebb301aa5d309a8ee6ea258cad680ea112c86e456d5f2254448afc74"; +process.env.MISHTI_CLIENT_PRIVATE_KEY = + "0x04d16281ff3bf268b29cdd684183f72542757d24ae9fdfb863e7c755e599163a"; +process.env.MISHTI_RELAY_URL = "http://127.0.0.1:8081"; diff --git a/iam/package.json b/iam/package.json index 6b6ee58549..44dae83246 100644 --- a/iam/package.json +++ b/iam/package.json @@ -31,9 +31,10 @@ "@gitcoin/passport-identity": "^1.0.0", "@gitcoin/passport-platforms": "^1.0.0", "@gitcoin/passport-types": "^1.0.0", + "@holonym-foundation/mishtiwasm": "^0.3.0-robustnet", + "@ipld/dag-cbor": "^7.0.3", "@spruceid/didkit-wasm-node": "^0.2.1", - "multiformats": "^13.0.1", - "axios": "^0.27.2", + "axios": "^1.7.9", "brightid_sdk": "^1.0.1", "cors": "^2.8.5", "dids": "^5.0.2", @@ -41,7 +42,6 @@ "ethers": "^6.13.4", "express": "4", "google-auth-library": "^7.14.1", - "@ipld/dag-cbor": "^7.0.3", "luxon": "^2.4.0", "moralis": "^2.24.2", "multiformats": "^9.9.0", diff --git a/iam/src/index.ts b/iam/src/index.ts index 958e37207f..0b1faa8c60 100644 --- a/iam/src/index.ts +++ b/iam/src/index.ts @@ -50,58 +50,25 @@ import { filterRevokedCredentials } from "./utils/revocations.js"; // ---- Config - check for all required env variables // We want to prevent the app from starting with default values or if it is misconfigured -const configErrors = []; - -if (!process.env.IAM_JWK) { - configErrors.push("IAM_JWK is required"); -} - -if (!process.env.ATTESTATION_SIGNER_PRIVATE_KEY) { - configErrors.push("ATTESTATION_SIGNER_PRIVATE_KEY is required"); -} - -if (!process.env.TESTNET_ATTESTATION_SIGNER_PRIVATE_KEY) { - configErrors.push("TESTNET_ATTESTATION_SIGNER_PRIVATE_KEY is required"); -} - -if (!process.env.ALLO_SCORER_ID) { - configErrors.push("ALLO_SCORER_ID is required"); -} - -if (!process.env.SCORER_ENDPOINT) { - configErrors.push("SCORER_ENDPOINT is required"); -} - -if (!process.env.SCORER_API_KEY) { - configErrors.push("SCORER_API_KEY is required"); -} - -if (!process.env.EAS_GITCOIN_STAMP_SCHEMA) { - configErrors.push("EAS_GITCOIN_STAMP_SCHEMA is required"); -} - -if (!process.env.MORALIS_API_KEY) { - configErrors.push("MORALIS_API_KEY is required"); -} - -if (!process.env.IAM_JWK_EIP712) { - configErrors.push("IAM_JWK_EIP712 is required"); -} - -if (!process.env.EAS_FEE_USD) { - configErrors.push("EAS_FEE_USD is required"); -} - -if (!process.env.SCROLL_BADGE_PROVIDER_INFO) { - configErrors.push("SCROLL_BADGE_PROVIDER_INFO is required"); -} - -if (!process.env.SCROLL_BADGE_ATTESTATION_SCHEMA_UID) { - configErrors.push("SCROLL_BADGE_ATTESTATION_SCHEMA_UID is required"); -} - -if (configErrors.length > 0) { - configErrors.forEach((error) => console.error(error)); // eslint-disable-line no-console +const missingEnvVars = [ + "IAM_JWK", + "ATTESTATION_SIGNER_PRIVATE_KEY", + "TESTNET_ATTESTATION_SIGNER_PRIVATE_KEY", + "ALLO_SCORER_ID", + "SCORER_ENDPOINT", + "SCORER_API_KEY", + "EAS_GITCOIN_STAMP_SCHEMA", + "MORALIS_API_KEY", + "IAM_JWK_EIP712", + "EAS_FEE_USD", + "SCROLL_BADGE_PROVIDER_INFO", + "SCROLL_BADGE_ATTESTATION_SCHEMA_UID", + "MISHTI_CLIENT_PRIVATE_KEY", + "MISHTI_RELAY_URL", +].filter((env) => !process.env[env]); + +if (missingEnvVars.length > 0) { + missingEnvVars.forEach((envVar) => console.error(`${envVar} is required`)); throw new Error("Missing required configuration"); } @@ -254,6 +221,7 @@ app.post("/api/v0.0.0/verify", (req: Request, res: Response): void => { return void errorRes(res, "Unable to verify payload", 401); }) .catch((error) => { + console.log("ERROR", error); if (error instanceof ApiError) { return void errorRes(res, error.message, error.code); } diff --git a/iam/src/missingTypes.d.ts b/iam/src/missingTypes.d.ts new file mode 100644 index 0000000000..39db6fe229 --- /dev/null +++ b/iam/src/missingTypes.d.ts @@ -0,0 +1,42 @@ +declare module "ceramic-cacao" { + export declare type Cacao = { + h: Header; + p: Payload; + s?: Signature; + }; + + export declare namespace Cacao { + function fromBlockBytes(bytes: Uint8Array): Promise; + } +} + +declare module "multiformats/cid" { + export class CID { + static decode(bytes: Uint8Array): CID; + toString(): string; + } +} + +declare module "multiformats/block" { + export class CID { + static decode(bytes: Uint8Array): CID; + toString(): string; + } + + export class Block { + cid: CID; + } + + export function encode({ + value, + codec, + hasher, + }: { + value: T; + codec: import("./codecs/interface").BlockEncoder; + hasher: import("./hashes/interface").MultihashHasher; + }): Promise>; +} + +declare module "multiformats/hashes/sha2"; +declare module "@ipld/dag-cbor"; diff --git a/iam/src/scripts/buildProviderBitMapInfo.ts b/iam/src/scripts/buildProviderBitMapInfo.ts index 18d9db7ae9..b631e068d6 100644 --- a/iam/src/scripts/buildProviderBitMapInfo.ts +++ b/iam/src/scripts/buildProviderBitMapInfo.ts @@ -3,7 +3,7 @@ import { writeFileSync } from "fs"; import { join } from "path"; import axios from "axios"; -import { StampMetadata, mapBitMapInfo } from "../utils/easPassportSchema"; +import { StampMetadata, mapBitMapInfo } from "../utils/easPassportSchema.js"; dotenv.config(); diff --git a/iam/src/utils/credentials.ts b/iam/src/utils/credentials.ts index 70f1896be7..9e9bcdfd34 100644 --- a/iam/src/utils/credentials.ts +++ b/iam/src/utils/credentials.ts @@ -8,6 +8,7 @@ import { ProviderContext, VerifiedPayload, VerifiableCredential, + ProofRecord, } from "@gitcoin/passport-types"; import { getIssuerKey } from "../issuers.js"; @@ -20,6 +21,7 @@ import { issueHashedCredential, verifyCredential } from "@gitcoin/passport-ident import { providers, platforms } from "@gitcoin/passport-platforms"; import { ApiError } from "./helpers.js"; import { checkCredentialBans } from "./bans.js"; +import { recordToNullifier } from "./oprf.js"; const providerTypePlatformMap = Object.entries(platforms).reduce( (acc, [platformName, { providers }]) => { @@ -66,7 +68,8 @@ const issueCredentials = async ( results.map(async ({ verifyResult, code: verifyCode, error: verifyError, type }) => { let code = verifyCode; let error = verifyError; - let record, credential; + let record: ProofRecord | undefined; + let credential; try { // check if the request is valid against the selected Identity Provider @@ -89,10 +92,12 @@ const issueCredentials = async ( address, record, verifyResult.expiresInSeconds, - payload.signatureType + payload.signatureType, + () => recordToNullifier({ record }) )); } - } catch { + } catch (e) { + console.error(e); error = "Unable to produce a verifiable credential"; code = 500; } diff --git a/iam/src/utils/oprf.ts b/iam/src/utils/oprf.ts new file mode 100644 index 0000000000..a160ee71d9 --- /dev/null +++ b/iam/src/utils/oprf.ts @@ -0,0 +1,45 @@ +// Need to do this here instead of in the identity package +// so that this isn't loaded in the browser + +// ---- Types +import { ProofRecord } from "@gitcoin/passport-types"; + +// ---- Generate & Verify methods +import { objToSortedArray } from "@gitcoin/passport-identity"; + +// All provider exports from platforms +import { readFileSync } from "fs"; +import { join, dirname } from "path"; + +import { initSync as mishtiInitSync, generate_oprf } from "@holonym-foundation/mishtiwasm"; + +let mishtiInitialized = false; +const initializeMishti = async () => { + if (mishtiInitialized) return; + + await Promise.resolve(); + const monorepoBaseDir = dirname(process.cwd()); + const wasmPath = join(monorepoBaseDir, "node_modules/@holonym-foundation/mishtiwasm/pkg/esm", "mishtiwasm_bg.wasm"); + + // console.log("Loading wasm module", wasmPath); + const wasmModuleBuffer = readFileSync(wasmPath); + + mishtiInitSync({ module: wasmModuleBuffer }); + + mishtiInitialized = true; +}; + +export const recordToNullifier = async ({ record }: { record: ProofRecord }) => { + const cleartextNullifier = JSON.stringify(objToSortedArray(record)); + await initializeMishti(); + + const nullifier = await generate_oprf( + process.env.MISHTI_CLIENT_PRIVATE_KEY, + cleartextNullifier, + "OPRFSecp256k1", + process.env.MISHTI_RELAY_URL + ); + + // console.log("nullifier", nullifier); + return nullifier; +}; diff --git a/iam/src/utils/scorerService.ts b/iam/src/utils/scorerService.ts index de5dc9416b..0def230c4d 100644 --- a/iam/src/utils/scorerService.ts +++ b/iam/src/utils/scorerService.ts @@ -1,5 +1,5 @@ import axios from "axios"; -import { Score } from "./easStampSchema"; +import { Score } from "./easStampSchema.js"; import { handleAxiosError } from "@gitcoin/passport-platforms"; export class IAMError extends Error { diff --git a/iam/src/utils/verifyDidChallenge.ts b/iam/src/utils/verifyDidChallenge.ts index 0d0da2eabb..15e2b7ef01 100644 --- a/iam/src/utils/verifyDidChallenge.ts +++ b/iam/src/utils/verifyDidChallenge.ts @@ -4,9 +4,8 @@ import { Cacao } from "ceramic-cacao"; import { CID } from "multiformats/cid"; import { SignedDidChallenge } from "@gitcoin/passport-types"; import * as dagCBOR from "@ipld/dag-cbor"; -import { encode } from "multiformats/block"; +import { encode, Block } from "multiformats/block"; import { sha256 } from "multiformats/hashes/sha2"; -import { MAX_VALID_DID_SESSION_AGE } from "@gitcoin/passport-identity"; export class VerifyDidChallengeBaseError extends Error {} @@ -36,7 +35,11 @@ const verifyMatchesExpectedChallenge = async ( expectedChallenge: string ): Promise => { try { - const expectedBlock = await encode({ value: expectedChallenge, codec: dagCBOR, hasher: sha256 }); + const expectedBlock: Block = await encode({ + value: expectedChallenge, + codec: dagCBOR, + hasher: sha256, + }); const signedCID = CID.decode(new Uint8Array(signedChallenge.cid)); diff --git a/iam/tsconfig.json b/iam/tsconfig.json index 1cdb01b9c5..cf299a73f3 100644 --- a/iam/tsconfig.json +++ b/iam/tsconfig.json @@ -1,13 +1,13 @@ { "extends": "../tsconfig.settings.json", "compilerOptions": { - "module": "esnext", + "module": "NodeNext", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "allowJs": true, "target": "es6", "noImplicitAny": true, - "moduleResolution": "node", + "moduleResolution": "nodenext", "sourceMap": true, "outDir": "dist", "baseUrl": ".", diff --git a/identity/jest.config.js b/identity/jest.config.js index 652c929fd7..8e2bba4554 100644 --- a/identity/jest.config.js +++ b/identity/jest.config.js @@ -4,4 +4,7 @@ module.exports = { "^.+\\.tsx?$": "ts-jest", }, modulePathIgnorePatterns: ["/dist/"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, }; diff --git a/identity/src/credentials.ts b/identity/src/credentials.ts index 3527456f26..197aa52dd0 100644 --- a/identity/src/credentials.ts +++ b/identity/src/credentials.ts @@ -32,7 +32,7 @@ import { challengeSignatureDocument, DocumentType, stampCredentialDocument, -} from "./signingDocuments"; +} from "./signingDocuments.js"; // Control expiry times of issued credentials export const CHALLENGE_EXPIRES_AFTER_SECONDS = 60; // 1min @@ -200,6 +200,30 @@ export const issueChallengeCredential = async ( } as IssuedCredential; }; +const getNullifier = async ({ + key, + record, + oprf, +}: { + key: string; + record: ProofRecord; + oprf?: () => Promise; +}) => { + if (oprf) { + return oprf(); + } else { + // Generate a hash like SHA256(IAM_PRIVATE_KEY+PII), where PII is the (deterministic) JSON representation + // of the PII object after transforming it to an array of the form [[key:string, value:string], ...] + // with the elements sorted by key + return base64.encode( + createHash("sha256") + .update(key, "utf-8") + .update(JSON.stringify(objToSortedArray(record))) + .digest() + ); + } +}; + // Return a verifiable credential with embedded hash export const issueHashedCredential = async ( DIDKit: DIDKitLib, @@ -207,17 +231,10 @@ export const issueHashedCredential = async ( address: string, record: ProofRecord, expiresInSeconds: number = CREDENTIAL_EXPIRES_AFTER_SECONDS, - signatureType?: string + signatureType?: string, + oprf?: () => Promise ): Promise => { - // Generate a hash like SHA256(IAM_PRIVATE_KEY+PII), where PII is the (deterministic) JSON representation - // of the PII object after transforming it to an array of the form [[key:string, value:string], ...] - // with the elements sorted by key - const hash = base64.encode( - createHash("sha256") - .update(key, "utf-8") - .update(JSON.stringify(objToSortedArray(record))) - .digest() - ); + const hash = await getNullifier({ key, record, oprf }); let credential: VerifiableCredential; if (signatureType === "EIP712") { diff --git a/yarn.lock b/yarn.lock index bf17aeda6d..6da6d9860f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6046,6 +6046,11 @@ resolved "https://registry.yarnpkg.com/@heroicons/react/-/react-2.0.17.tgz#42a8086bc434ceefc03592f20c4e81b11e915cf8" integrity sha512-90GMZktkA53YbNzHp6asVEDevUQCMtxWH+2UK2S8OpnLEu7qckTJPhNxNQG52xIR1WFTwFqtH6bt7a60ZNcLLA== +"@holonym-foundation/mishtiwasm@^0.3.0-robustnet": + version "0.3.0-robustnet" + resolved "https://registry.yarnpkg.com/@holonym-foundation/mishtiwasm/-/mishtiwasm-0.3.0-robustnet.tgz#621cec90bae996aeb0eb396f77d256b3abe81668" + integrity sha512-0wCFQdeDx1XDsKcc7/jnnADY60kBf1HhbswS5rZ46kD9ZbPzm1LDYAmC5k7FLmgaCmyJdeGewLiSX1xSzFOFZQ== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -11477,9 +11482,9 @@ agentkeepalive@^4.1.3: humanize-ms "^1.2.1" agentkeepalive@^4.2.1: - version "4.5.0" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + version "4.6.0" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz#35f73e94b3f40bf65f105219c623ad19c136ea6a" + integrity sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== dependencies: humanize-ms "^1.2.1" @@ -12020,6 +12025,15 @@ axios@^1.2.1: form-data "^4.0.0" proxy-from-env "^1.1.0" +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + axobject-query@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" @@ -16240,6 +16254,11 @@ follow-redirects@^1.12.1, follow-redirects@^1.14.0, follow-redirects@^1.14.4, fo resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"