import { CeramicClient } from "@ceramicnetwork/http-client"; import { encodeDIDWithLit, Secp256k1ProviderWithLit } from "key-did-provider-secp256k1-with-lit"; import { DID } from "dids"; import { getResolver } from "key-did-resolver"; import { TileDocument } from "@ceramicnetwork/stream-tile"; import { splitSignature } from "@ethersproject/bytes"; import { computePublicKey, recoverPublicKey } from "@ethersproject/signing-key"; import { verifyMessage } from "@ethersproject/wallet"; import verifyCredential from "./verifyCredential"; import { Credential, Proof, VerifyPresentationResponse } from "./interfaces"; const verifyCredentialInPresentation = (recoveredCredential: Credential) => { let proofObj: Proof | undefined; const originalCredential = JSON.parse(JSON.stringify(recoveredCredential)); proofObj = recoveredCredential.proof; delete originalCredential.proof; const stringifiedOriginalCredential = JSON.stringify(originalCredential); // @ts-ignore const encodedSig = proofObj.jws; // @ts-ignore const dataSigned = recoveredCredential.proof.verificationMethod; const recoveredPubkey = recoverPublicKey(dataSigned, encodedSig); const compressedRecoveredPubkey = computePublicKey(recoveredPubkey, true); const recoveredAddressViaMessage = verifyMessage(stringifiedOriginalCredential, encodedSig); return recoveredCredential.issuer === compressedRecoveredPubkey.slice(2) && !!recoveredAddressViaMessage; } export default async function verifyPresentation(vcId: string, issuer: string): Promise { let encodedDID: string; let doc; let docId: string; let credentialsVerified = false; // acceptPresentation will initially get and show the presentation. actual acceptance functionality will come later const ceramicNetwork = process.env?.REACT_APP_CERAMIC_NETWORK ?? "https://ceramic-clay.3boxlabs.com"; const ceramic = new CeramicClient(ceramicNetwork); encodedDID = await encodeDIDWithLit({ pkpPublicKey: issuer }); const provider = new Secp256k1ProviderWithLit({ did: encodedDID, ipfsId: "QmcZ2MuxkNrMbNKAVtK37tEmKJ8zwvFudin3rBEcHyhqJc", }); const did = new DID({provider, resolver: getResolver()}); // -- authenticate await did.authenticate(); ceramic.did = did; // -- read a ceramic stream var loadDoc = await TileDocument.load(ceramic, vcId); const recoveredPresentation: any = loadDoc.content console.log('$$$$$ -> load doc', recoveredPresentation) const credentialStatus = recoveredPresentation.verifiableCredential.map((recoveredCredential: Credential) => { return verifyCredentialInPresentation(recoveredCredential); }); let credentialStatusSet = new Set(credentialStatus); let credentialStatusArray = Array.from(credentialStatusSet); if (credentialStatusArray.length === 1 && credentialStatusArray[0] === true) { credentialsVerified = true; } // deep copy presentation and delete `proof` property to get original presentation const originalPresentation = JSON.parse(JSON.stringify(recoveredPresentation)); const proofObj = recoveredPresentation.proof; delete originalPresentation.proof; const stringifiedOriginalPresentation = JSON.stringify(originalPresentation); const encodedSig = proofObj.jws; const dataSigned = recoveredPresentation.proof.verificationMethod; const recoveredPubkey = recoverPublicKey(dataSigned, encodedSig); const compressedRecoveredPubkey = computePublicKey(recoveredPubkey, true); const recoveredAddressViaMessage = verifyMessage(stringifiedOriginalPresentation, encodedSig); return { recoveredPresentation, verified: recoveredPresentation.issuer === compressedRecoveredPubkey.slice(2) && !!recoveredAddressViaMessage && !!credentialsVerified }; }