tydids-core
Version:
A JavaScript library that empowers user defined Self-Sovereign Identity & Consent Management
373 lines (342 loc) • 17.2 kB
JavaScript
/**
* The Validation class provides functionalities for managing validation ID related data and interacting with a validation contract.
* This class allows you to create validation ID objects, attach secure elements, set validation data, sign the data,
* and publish or revoke the validation on a tydids blockchain.
*
* Creation:
* - Instantiate a new Validation object using `new Validation()`.
* Data Population:
* - Use `setValidationObject()` to directly define key-value pairs (key_1, value_1, key_2, value_2) for the validation data.
* - Utilize `addPDF()` to attach a PDF file as validation data.
* Signing:
* - Invoke `signAccountData()` to generate and set the accountSignature.
* Data Retrieval:
* - Employ `toJSON()` to obtain a JSON representation of the validation data, including signatures.
* Secure Element Management:
* - Generate a new secure element using `retrieveSecureElement()` and store it securely.
* - Attach an existing secure element using `attachSecureElement()` to enable signing and contract interaction.
* Publication and Revocation:
* - Use `publish()` to announce the validation on the blockchain (requires an attached secure element).
* - Employ `revoke()` to invalidate the validation on the blockchain (requires an attached secure element).
* Persistence:
* - Save the validation data as a JSON string using `toJSON()`.
* - Restore the validation data from a JSON string using `openJSON()` on a newly created Validation instance.
*/
const ethers = require("ethers");
const axios = require('axios');
const env = require("./Env.js");
class Validation {
/**
* Constructs a new instance of the class. Initializes the object with default values for the following properties:
* - validationSecureElement: a randomly generated ethers.Wallet object
* - isMutable: a boolean indicating whether the object is mutable or not
* - validationData: an empty object to store validation data
* - validationSignature: an empty string for the validation signature
* - accountData: an empty object to store account data
* - accountSignature: an empty string for the account signature
* - validationID: the address of the validation secure element
* - account: a string representing the account address ("0x0" by default)
* - isSigned: a boolean indicating whether the object is signed or not
*/
constructor() {
this.validationSecureElement = ethers.Wallet.createRandom();
this.isMutable = true;
this.validationObject = {};
this.validationData = "";
this.validationSignature = "";
this.accountData = {};
this.accountSignature = "";
this.validationID = this.validationSecureElement.address;
this.account = "0x0";
this.isSigned = false;
this.provider = new ethers.providers.JsonRpcProvider(env._RPC_URL);
this.validationSecureElement = this.validationSecureElement.connect(this.provider)
}
/**
* Returns an object containing the validation data, validation signature, account data, and account signature.
*
* @return {Object} An object with the following properties:
* - validationData: The validation data stored in the object.
* - validationSignature: The validation signature stored in the object.
* - accountData: The account data stored in the object.
* - accountSignature: The account signature stored in the object.
*/
getFields() {
return {
validationData: this.getValidationObjectToData(),
validationSignature: this.validationSignature,
accountData: encodeURIComponent(JSON.stringify(this.accountData)),
accountSignature: this.accountSignature
}
}
/**
* Converts the object to a JSON string representation by calling the `getFields()` method and
* using `JSON.stringify()` to serialize the result.
*
* @return {string} A JSON string representation of the object.
*/
toJSON() {
return JSON.stringify(this.getFields());
}
/**
* Asynchronously retrieves the secure element by encrypting it with the provided password.
*
* @param {string} password - The password used to encrypt the secure element.
* @return {Promise<string>} A promise that resolves to the encrypted secure element.
*/
async retrieveSecureElement(password) {
return await this.validationSecureElement.encrypt(password);
}
/**
* Asynchronously attaches a secure element to the current object by decrypting the provided encrypted JSON
* using the provided password. If the decrypted address does not match the validation ID, an Error is thrown.
*
* @param {string} encryptedJSON - The encrypted JSON representing the secure element.
* @param {string} password - The password used to decrypt the secure element.
* @return {Promise<void>} A Promise that resolves when the secure element is successfully attached.
* @throws {Error} If the decrypted address does not match the validation ID.
*/
async attachSecureElement(encryptedJSON,password) {
this.isMutable = false;
this.validationSecureElement = await ethers.Wallet.fromEncryptedJson(encryptedJSON,password);
if(this.validationSecureElement.address !== this.validationID) throw Error("Secure Element does not match Validation");
this.validationSecureElement = this.validationSecureElement.connect(this.provider);
this.isMutable = true;
}
async addPDF(pdf64,filename) {
const data = {
did:JSON.stringify({pdf64: pdf64})
}
const response = await axios.post('https://api.corrently.io/v2.0/ipfs/announce', data)
this.validationObject.key_1 = "Attachment";
this.validationObject.value_1 = filename;
this.validationObject.key_2 = "Hash";
this.validationObject.value_2 = response.data.Hash;
}
/**
* Open the fields of the validation object by populating the object with the provided data and signatures.
*
* @param {string} validationData - The validation data to populate the object with.
* @param {string} validationSignature - The signature of the validation data.
* @param {string} accountData - The account data to populate the object with.
* @param {string} accountSignature - The signature of the account data.
* @throws {Error} If the validation ID or iat is not specified in the validation data.
* @throws {Error} If the account is not specified in the validation data.
* @throws {Error} If the validation signature does not match the validation ID.
* @throws {Error} If the account signature does not match the account.
*/
openFields(validationData, validationSignature, accountData, accountSignature) {
this.isMutable = false;
this.validationSecureElement = ethers.Wallet.createRandom();
this.validationSecureElement = this.validationSecureElement.connect(this.provider)
// Signature Validation (Consistency)
const accountConsens = ethers.utils.verifyMessage(accountData,accountSignature);
// Populate Object instance
this.validationObject = decodeURIComponent(validationData);
try {
while(typeof this.validationObject !== 'object') {
this.validationObject = JSON.parse(this.validationObject);
}
} catch(e) {
throw Error("Unable to parse JSON to create validationObject");
}
const validationConsens = ethers.utils.verifyMessage(this.getValidationObjectToData(),validationSignature);
if(typeof this.validationObject.validationID !== 'undefined') this.validationID = this.validationObject.validationID; else throw Error("validationID not specified");
if(typeof this.validationObject.iat !== 'undefined') this.iat = this.validationObject.iat; else throw Error("iat not specified");
if(typeof this.validationObject.account !== 'undefined') this.account = this.validationObject.account; else throw Error("account not specified");
this.validationSignature = validationSignature;
this.accountData = JSON.parse(decodeURIComponent(accountData));
while(typeof this.accountData !== 'object') {
this.accountData = JSON.parse(this.accountData);
}
this.accountSignature = accountSignature;
// Ensure Consensus
if(this.validationID !== validationConsens) throw new Error("validationSignature broken");
if(this.account !== accountConsens) throw new Error("accountSignature broken");
this.validationData = this.getValidationObjectToData();
}
/**
* Parses the provided JSON string and opens the fields using the `openFields` method.
*
* @param {string|object} json - The JSON string or object to parse.
* @return {Promise<void>} A promise that resolves when the fields are opened.
*/
openJSON(json) {
while(typeof json !== 'object') {
json = JSON.parse(json);
}
return this.openFields(json.validationData,json.validationSignature,json.accountData,json.accountSignature);
}
/**
* Returns the validation data as a URI-encoded JSON string.
*
* @return {string} The URI-encoded JSON string representation of the validation data.
*/
getValidationObjectToData() {
return encodeURIComponent(JSON.stringify(this.validationObject));
}
/**
* Sets the validation data for the object, ensuring that only allowed fields are present.
*
* @param {Object} obj - The object containing the validation data to set.
* @throws {Error} If the object is not mutable.
* @return {Promise<void>} A promise that resolves when the validation data has been set.
*/
async setValidationObject(obj) {
if(!this.isMutable) throw Error("Validation is imutable");
// we only allow key_1,key_2,value_1 and value_2 as fields
for (const [key, value] of Object.entries(obj)) {
if(
(key !== 'key_1') &&
(key !== 'key_2') &&
(key !== 'value_1') &&
(key !== 'value_2')
) delete obj[key];
}
this.validationObject = JSON.parse(JSON.stringify(obj)); // DeepCopy
this.validationObject.account = this.account;
this.validationObject.validationID = this.validationID;
this.validationObject.iat = Math.floor(Date.now()/1000);
this.validationSignature = await this.validationSecureElement.signMessage(this.getValidationObjectToData());
this.accountData = {
validationSignature: this.validationSignature,
validationID: this.validationID,
account: this.account
};
this.validationData = this.getValidationObjectToData();
this.accountSignature = null;
this.isSigned = false;
}
/**
* Announces the current validation by calling the `publish` function of the `Contract` object.
*
* @return {Promise<ethers.providers.TransactionReceipt>} A promise that resolves to the transaction receipt of the publish transaction.
*/
async publish() {
const sc = new ethers.Contract(env._publishContract, env._publishAbi, this.validationSecureElement);
const rcp = await sc.publish();
const receipt = await rcp.wait();
return receipt;
}
/**
* Checks if the current validation is published by calling the `publishs` function of the `Contract` object.
*
* @return {Promise<any>} A promise that resolves to the result of the `publishs` function with time stamp of publishing or 0
*/
async isPublishedAt() {
const sc = new ethers.Contract(env._publishContract, env._publishAbi, this.validationSecureElement);
const rcp = await sc.publishs(this.validationID);
return rcp.toString() * 1;
}
/**
* Asynchronously revokes this validation by calling the `revoke` function on the contract instance
* and waiting for the transaction to be mined. Returns the transaction receipt.
*
* @return {Promise<object>} The transaction receipt object.
*/
async revoke() {
const sc = new ethers.Contract(env._revokeContract, env._revokeAbi, this.validationSecureElement);
const rcp = await sc.revoke();
const receipt = await rcp.wait();
return receipt;
}
/**
* Asynchronously upvotes a given address by calling the `upvote` function on the contract instance
* and waiting for the transaction to be mined. Returns the transaction receipt.
*
* @param {string} address - The address to upvote.
* @return {Promise<object>} The transaction receipt object.
*/
async upvote(address) {
const sc = new ethers.Contract(env._voteContract, env._voteABI, this.validationSecureElement);
const rcp = await sc.upvote(address);
const receipt = await rcp.wait();
return receipt;
}
/**
* Asynchronously downvotes a given address by calling the `downvote` function on the contract instance
* and waiting for the transaction to be mined. Returns the transaction receipt.
*
* @param {string} address - The address to downvote.
* @return {Promise<object>} The transaction receipt object.
*/
async downvote(address) {
const sc = new ethers.Contract(env._voteContract, env._voteABI, this.validationSecureElement);
const rcp = await sc.downvote(address);
const receipt = await rcp.wait();
return receipt;
}
/**
* Asynchronously retrieves the number of upvotes and downvotes for a this validation ID.
*
* @return {Promise<Object>} An object containing the number of upvotes and downvotes.
* The object has the following properties:
* - upvotes: The number of upvotes as a number.
* - downvotes: The number of downvotes as a number.
*/
async votes() {
const sc = new ethers.Contract(env._voteContract, env._voteABI, this.validationSecureElement);
const upvotes = await sc.upvoteCount(this.validationID);
const downvotes = await sc.downvoteCount(this.validationID);
return {
upvotes: upvotes.toString() * 1,
downvotes: downvotes.toString() * 1
};
}
/**
* Asynchronously retrieves the list of upvotes and downvotes for validation ID.
*
* @return {Promise<Object>} An object containing the list of upvotes and downvotes.
* The object has the following properties:
* - upvoters: An array of upvoters' addresses.
* - downvoters: An array of downvoters' addresses.
*/
async listVotes() {
const cnt = await this.votes();
const sc = new ethers.Contract(env._voteContract, env._voteABI, this.validationSecureElement);
let upvotes = [];
for(let i=0;i<cnt.upvotes;i++) {
upvotes.push(await sc.upvoters(this.validationID,i));
}
let downvotes = [];
for(let i=0;i<cnt.downvotes;i++) {
downvotes.push(await sc.downvoters(this.validationID,i));
}
return {
upvoters:upvotes,
downvoters:downvotes
};
}
/**
* Asynchronously checks if the current validation is revoked by calling the `revocations` function on the contract instance
* and waiting for the result. Returns the result as a time of revocation or 0.
*
* @return {Promise<number>} Returns revocation timestamp or 0 if not revoked.
*/
async isRevokedAt() {
const sc = new ethers.Contract(env._revokeContract, env._revokeAbi, this.validationSecureElement);
const rcp = await sc.revocations(this.validationID);
return rcp.toString() * 1;
}
/**
* Attaches a signer to the current instance.
*
* @param {Object} signer - The signer object.
* @return {void}
*/
attachSigner(signer) {
this.signer = signer;
this.account = signer.address;
this.isMutable = true;
}
/**
* Signs the account data using the signer and updates the account signature and isSigned status.
*
* @return {Promise<void>} A Promise that resolves when the account data is signed.
*/
async signAccountData() {
this.accountSignature = await this.signer.signMessage(this.getFields().accountData);
this.isSigned = true;
}
}
module.exports = Validation;