import { MamReader, MAM_MODE } from "mam.ts"; import { VCState, ClaimTx, CLAIM_COMMANDS } from "./VCState"; import { Attribute, MerkleHash, GenerateMerkleRoot, oneWayHash } from "../MerkleTree"; import { RsaKeypair } from '../RsaKeypair'; import { DIDReader } from '../DecentralizedIdentifier/DIDReader'; import axios from 'axios'; import tv4 = require("tv4"); export interface schema { "version" : { "type": "string" }; "command" : { "type": "string" }; "data" : {}; } const baseSchema : schema = { "version": { "type": "string" }, "command" : { "type": "string" }, "data": {} }; export interface IssuerSiteData { url : string, logoUrl: string } export class VCReader{ private mamReader : MamReader; private state : VCState; constructor( provider : string, verifiableClaimStreamRoot : string) { this.mamReader = new MamReader(provider, verifiableClaimStreamRoot, MAM_MODE.PUBLIC) this.state = new VCState(); } public async queryVC() : Promise { return new Promise((resolve, reject) => { this.mamReader.fetch() .then((Messages) => { for(let i=0; i < Messages.length; i++) { let Message : ClaimTx = JSON.parse(Messages[i]); switch(Message.command) { case CLAIM_COMMANDS.ADD_CLAIM: if(this.validateTxToSchema(Message.command, Message.data)){ this.ProcessAddClaim(Message.data, Message.proof); } else { reject("The incoming data is not valid."); } break; default: reject("Unsupported command"); break; } } resolve(true); }) .catch((error) => { reject(`Failed to read the MAM stream: ${error}`); }); }); } private validateTxToSchema(command:CLAIM_COMMANDS, txData:{}) : Boolean{ let schema : schema = baseSchema; switch(command){ case CLAIM_COMMANDS.ADD_CLAIM: schema.data = { "signature": { "type": "string" }, "holderhash" : { "type": "string" } }; break; default: console.log("Unexpected schema.") } return tv4.validate(txData, schema); } private ProcessAddClaim(txData : {}, txProof : {}) { this.state.UpdateState(txData["signature"], txData["holderhash"]); } public async VerifyClaim(uuidHolder : string, salt : string, issuerUUID : string, claimAttributes: Attribute[], claimHashes : MerkleHash[]) : Promise { return new Promise ((resolve, reject) => { //Read the DID of the Issuer let didReader = new DIDReader("https://nodes.thetangle.org:443", issuerUUID); didReader.QueryDID().then(()=>{ let IssuerState = didReader.GetState(); let HolderHashResult : boolean = this.VerifyHolderHash(uuidHolder, salt); let SignatureResult : boolean = this.VerifySignature(IssuerState.getPublicKey()[0].publicKeyPem, claimAttributes, claimHashes); this.VerifyIssuer(issuerUUID, IssuerState.getServicePoints()[0].serviceEndpoint) .then((IssuerResult) => { if(HolderHashResult && SignatureResult) { resolve(IssuerResult); } else { reject("HolderHash or Signature failed"); } }).catch((err) => { reject(err); }); }); }); } public VerifySignature(publicKey : string, claimAttributes : Attribute[], claimHashes : MerkleHash[] = []) : boolean { //Generate the MerkleHash let MerkleHash = GenerateMerkleRoot(claimAttributes, claimHashes); let keypair : RsaKeypair; if(publicKey.includes("RSA PUBLIC KEY")) { keypair = new RsaKeypair(publicKey, undefined); } else { console.log("Invalid public key provided"); return false; } return keypair.Verify(MerkleHash, this.state.GetSignature()); } public VerifyHolderHash(uuidHolder : string, salt : string) : boolean { return oneWayHash(uuidHolder+salt) === this.state.GetHolderHash() ? true : false; } public async VerifyIssuer( issuerUUID : string, IssuerURL: string) : Promise { return new Promise ((resolve, reject) => { //Issuer Verification axios.get<{ logoUrl : string, did : string}>(IssuerURL) .then(res => { if(res.data.did.slice(res.data.did.lastIndexOf(':')+1) == issuerUUID) { let Result : IssuerSiteData = {url: res.data.did, logoUrl: res.data.logoUrl}; resolve(Result); } else { reject(`Incorrect UUID found on URL. No trust can be placed in this DID`); } }) .catch(error => { reject(`HTTP Request error: ${error}`); }); }); } public GetState() : VCState { return this.state; } }