Created
January 13, 2023 17:30
-
-
Save kamescg/ec269c6bf29b598a91fe2b62886895b5 to your computer and use it in GitHub Desktop.
vereifyVC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getResolver } from '@ceramicnetwork/3id-did-resolver'; | |
import { Caip10Link } from '@ceramicnetwork/stream-caip10-link'; | |
import { | |
recoverTypedSignature, | |
SignTypedDataVersion, | |
} from '@metamask/eth-sig-util'; | |
import { decodeJWT, verifyJWS } from 'did-jwt'; | |
import { Resolver } from 'did-resolver'; | |
import { | |
Missing712DomainException, | |
Missing712ProofException, | |
SignatureMismatchException, | |
} from '@/errors'; | |
// import { ceramicHttpClient } from '@/ceramic'; | |
import { ceramicHttpClient } from '../ceramic'; | |
import { ACCOUNT_ID_SUFFIX } from '../constants'; | |
const JWT_REGEX = /^([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)\.([a-zA-Z0-9_-]+)$/; | |
const VC_EXAMPLE = | |
'{"@context":["https://www.w3.org/2018/credentials/v1"],"credentialSchema":{"id":"https://raw.githubusercontent.com/discoxyz/disco-schemas/main/json/OfficialDisconautCredential/1-0-0.json","type":"JsonSchemaValidator2018"},"credentialSubject":{"id":"did:3:kjzl6cwe1jw149lm679w23b6waiflhhcl2129wr1lbh3nfz2cgpu3br8b1ljwnk"},"id":"did:3:kjzl6cwe1jw14a7u9sx3thx9gg9uh7u5tqjkzcnr5pi5zzkap7kiztgsfhzayzt#f560a30c-421f-4a7b-a12c-7dcb0e958859","issuanceDate":"2023-01-09T23:48:09.889Z","issuer":{"id":"did:3:kjzl6cwe1jw14a7u9sx3thx9gg9uh7u5tqjkzcnr5pi5zzkap7kiztgsfhzayzt"},"type":["VerifiableCredential","OfficialDisconautCredential"],"genId":"e0c6aeea-ad3d-497c-be2c-9942fda945cc","proof":{"jwt":"eyJraWQiOiJkaWQ6MzpranpsNmN3ZTFqdzE0YTd1OXN4M3RoeDlnZzl1aDd1NXRxamt6Y25yNXBpNXp6a2FwN2tpenRnc2ZoemF5enQ_dmVyc2lvbi1pZD0wI3BhRGp4Q2VnQjZaeU02SiIsImFsZyI6IkVTMjU2SyJ9.eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJjcmVkZW50aWFsU2NoZW1hIjp7ImlkIjoiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2Rpc2NveHl6L2Rpc2NvLXNjaGVtYXMvbWFpbi9qc29uL09mZmljaWFsRGlzY29uYXV0Q3JlZGVudGlhbC8xLTAtMC5qc29uIiwidHlwZSI6Ikpzb25TY2hlbWFWYWxpZGF0b3IyMDE4In0sImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOjM6a2p6bDZjd2UxancxNDlsbTY3OXcyM2I2d2FpZmxoaGNsMjEyOXdyMWxiaDNuZnoyY2dwdTNicjhiMWxqd25rIn0sImlkIjoiZGlkOjM6a2p6bDZjd2UxancxNGE3dTlzeDN0aHg5Z2c5dWg3dTV0cWpremNucjVwaTV6emthcDdraXp0Z3NmaHpheXp0I2Y1NjBhMzBjLTQyMWYtNGE3Yi1hMTJjLTdkY2IwZTk1ODg1OSIsImlzc3VhbmNlRGF0ZSI6IjIwMjMtMDEtMDlUMjM6NDg6MDkuODg5WiIsImlzc3VlciI6eyJpZCI6ImRpZDozOmtqemw2Y3dlMWp3MTRhN3U5c3gzdGh4OWdnOXVoN3U1dHFqa3pjbnI1cGk1enprYXA3a2l6dGdzZmh6YXl6dCJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT2ZmaWNpYWxEaXNjb25hdXRDcmVkZW50aWFsIl19.tFc2yNbsLxA8nWCqcSOHUFTFD5xwJ6udaKtw9iZ0QAoUlvmq7ETFSqykx0nxPwWIu4G_0zHTy7l_QuhcEyNb_Q"},"isPublic":false,"recipient":"did:3:kjzl6cwe1jw149lm679w23b6waiflhhcl2129wr1lbh3nfz2cgpu3br8b1ljwnk","updatedAt":"2023-01-09T23:48:09.889Z"}'; | |
export async function retrieveDidDocument(did: string) { | |
const threeidresolver = getResolver(ceramicHttpClient); | |
const resolver = new Resolver(threeidresolver); | |
const doc = await resolver.resolve(did); | |
return doc; | |
} | |
export async function verifyJwtVc(vc: string) { | |
const TypedData = JSON.parse(vc); | |
console.log(TypedData, 'TypedData'); | |
const decoded = decodeJWT(TypedData.proof.jwt).payload; | |
if (!decoded || !decoded?.issuer) throw new Error('Decoding JWT'); | |
const issuer = | |
typeof decoded.issuer === 'string' ? decoded.issuer : decoded.issuer.id; | |
const doc = await retrieveDidDocument(issuer); | |
if (!doc.didDocument || !doc.didDocument.verificationMethod) | |
throw new Error('Could not fetch did doc'); | |
const verified = await verifyJWS(vc, doc.didDocument?.verificationMethod!); | |
return !!verified; | |
} | |
export async function verify712Vc(vc: string) { | |
try { | |
const TypedData = JSON.parse(vc); | |
if (!TypedData.proof || !TypedData.proof.proofValue) | |
throw new Missing712ProofException(); | |
if ( | |
!TypedData.proof.eip712Domain || | |
!TypedData.proof.eip712Domain.messageSchema || | |
!TypedData.proof.eip712Domain.domain | |
) | |
throw new Missing712DomainException(); | |
const { proof, ...signingInput } = TypedData; | |
const { proofValue, eip712Domain, ...verifyInputProof } = proof; | |
const verificationMessage = { | |
...signingInput, | |
proof: verifyInputProof, | |
}; | |
const objectToVerify = { | |
message: verificationMessage, | |
domain: eip712Domain.domain, | |
types: eip712Domain.messageSchema, | |
primaryType: eip712Domain.primaryType, | |
}; | |
const recovered = recoverTypedSignature({ | |
data: objectToVerify, | |
signature: proofValue, | |
version: SignTypedDataVersion.V4, | |
}); | |
// Get did from address using CAIP 10 | |
const { did } = await Caip10Link.fromAccount( | |
ceramicHttpClient, | |
recovered + ACCOUNT_ID_SUFFIX | |
); | |
if (did === signingInput.issuer.id) { | |
return TypedData; | |
} | |
// @ts-ignore | |
throw new SignatureMismatchException(did, signingInput.issuer.id); | |
} catch (e: any) { | |
console.log(e); | |
throw e; | |
} | |
} | |
// @ts-ignore | |
export async function verifyEIP712VerifiableCredentialV2(_vc: string) { | |
return VC_EXAMPLE.match(JWT_REGEX) | |
? verifyJwtVc(VC_EXAMPLE) | |
: verify712Vc(VC_EXAMPLE); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment