import { VCState } from "./VCState"; import { MamWriter, MAM_MODE, keyGen } from "mam.ts"; import { Transaction } from "@iota/core"; import { VCReader } from "./VCReader"; import { isTrytesOfExactLength } from "@iota/validators"; import { Attribute, MerkleHash, GenerateMerkleRoot, oneWayHash } from "../MerkleTree"; import { RsaKeypair } from "../RsaKeypair"; import { subseed } from "@iota/signing"; export interface HolderInformation{ salt:string; root:string; uuid:string; keyId:string; } export class VCWriter{ private MAMWriter : MamWriter; private vcstate : VCState; private rootOfRoots : string; /** * Creates a verifiable claim writer and updates the internal state to be caught up with the state on IOTA. * @param provider * @param issuerSeed * @param subSeedIndex */ static async createVCWriter(provider : string, issuerSeed : string, subSeedIndex ?: number) : Promise { return new Promise(async (resolve, reject) => { //Generate subseed let subSeed : string = issuerSeed; /*if(subSeedIndex) { subSeed = subseed(issuerSeed, subSeedIndex); }*/ subSeed.replace('B', 'C'); subSeed.replace('D', 'K'); //Check initial state of the variables if(provider.length <= 0 && !isTrytesOfExactLength(subSeed, 81)) { reject(`Invalid Arguments supplied`); return; } //Creates the Writer let VcWriter : VCWriter = new VCWriter(provider, subSeed); VcWriter.MAMWriter.catchUpThroughNetwork() .then((txs)=> { //Creates a reader in order to catch up on the internal state of the VC stream on IOTA let reader = new VCReader(provider,VcWriter.GetVerifiableClaimStreamRoot()); reader.queryVC() .then(()=> { //Updates the state VcWriter.vcstate = reader.GetState(); resolve(VcWriter); }) .catch((error)=> { reject(`Verifiable Claim stream failed to catch up: ${error}`); }); }) .catch((error)=> { reject(`MamWriter failed to catch up: ${error}`); }); }); } //TODO: Subseed = new seed private constructor(provider : string, issuerSeed : string, subSeedIndex ?: number) { this.vcstate = null; this.MAMWriter = new MamWriter(provider, issuerSeed, MAM_MODE.PUBLIC); this.rootOfRoots = this.MAMWriter.getNextRoot(); } /** * Publishes the transaction to the IOTA network * @param transaction */ private async publishToMAM(transactionData : string) : Promise{ return new Promise((resolve, reject) => { this.MAMWriter.createAndAttach(transactionData) .then((transactions : Transaction[]) => { resolve(transactions); }) .catch((error)=> { reject(`Failed to publish to IOTA: ${error}`); }); }); } /** * Add a Verifiable Claim to the IOTA ledger. * This function first uses the attributes and merklehashes to generate a merkletree hash. * Afterwards the Holderhash is created * Lastly, the merklehash is signed using the privatekey and together with the holderhash posted to the MAM stream on IOTA. * @param claimAttributes * @param claimHashes * @param uuidHolder * @param privateKey */ public async AddClaim( uuidHolder : string, uuidIssuer : string, privateKey : string, claimAttributes : Attribute[]) : Promise { return new Promise((resolve, reject) => { //Generate the MerkleHash let MerkleHash = GenerateMerkleRoot(claimAttributes, []); //Generate the Holderhash let salt : string = keyGen(12); let HolderHash : string = VCWriter.GenerateHolderHash(uuidHolder, salt); //Sign the claim let rsaKeys : RsaKeypair = new RsaKeypair(undefined, privateKey); let Signature : string; if (!(Signature = VCWriter.Sign(MerkleHash, rsaKeys.GetPrivateKey())).length) { //If it returns an empty string reject(`Signature failed due to incorrect private key`); } //Create the transactionData let TransactionData = { "version" : "0.1.0", "command" : "addClaim", "data" : { "signature" : Signature, "holderhash" : HolderHash, } }; //Publish the claim this.publishToMAM(JSON.stringify(TransactionData)) .then((transactions : Transaction[])=> { this.vcstate.UpdateState(Signature, HolderHash); let Result : HolderInformation = { salt:salt, root:this.rootOfRoots, uuid:uuidIssuer, keyId:rsaKeys.GetPublicKey() } resolve(Result); }) .catch((error) => { reject(error); }); }); } private static GenerateHolderHash(uuidHolder : string, salt : string) : string { return oneWayHash(uuidHolder + salt); } private static Sign(HashToSign : string, privateKey : string) : string { let keypair : RsaKeypair; if(privateKey.includes("RSA PRIVATE KEY")) { keypair = new RsaKeypair(undefined, privateKey); } else { return ""; } return keypair.Sign(HashToSign); } public GetVerifiableClaimStreamRoot() : string { return this.rootOfRoots; } public GetVCState() : VCState { return this.vcstate; } }