@foreverrbum/ethsign
Version:
This package will allow you to electronically sign documents within your application
428 lines (377 loc) • 18.8 kB
JavaScript
import bs58 from 'bs58';
import CryptoJS from 'crypto-js';
import { create } from 'ipfs-http-client';
const FLEEK_SECRET = "hu1j3Lr1jZbpLF1L96HgKtjbtQCvHgw+J+Ww4dwIXHI=";
const FLEEK_KEY = "9mvRajFjGL6fws3M2BHJvA==";
import fleekStorage from '@fleekhq/fleek-storage-js'
import { ethers } from 'ethers';
import {storeNotif} from './dashboard';
import { getDocument, getDocumentDetails, getNewDocumentKey} from './dashboard';
import {signDocumentAtIndex} from './sign';
import { getFileName } from './file';
import { PDFDocument } from 'pdf-lib';
const fromAscii = ethers.utils.formatBytes32String;
export const handleFileUpload = async (storage_provider, buffer) => {
let storage_ID;
if (storage_provider == "IPFS") {
} else if (storage_provider == "Fleek") {
}
return storage_ID;
}
export const handleDocumentReupload = async (storage_provider, handleSubmitButton, password, documentKey, file, fileHash, handleFileHash, createProgress, handleCreateProgress, indicatorProgress, handleIndicatorProgress, continueCallback, close, formatMessage) => {
let docuStorageHash = fileHash;
const reader = new window.FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = async () => {
// File encryption
handleSubmitButton(formatMessage({id: 'ENCRYPTING_FILE'}))
const buffer = Buffer(reader.result);
const encryptedString = CryptoJS.AES.encrypt(
JSON.stringify(buffer),
password
).toString();
// File upload
// Part 1 and part 2 if cannot fit in bytes32
let storage_id0 = '0x0000000000000000000000000000000000000000000000000000000000000000';
let storage_id1 = '0x0000000000000000000000000000000000000000000000000000000000000000';
try {
handleSubmitButton(formatMessage({id: 'UPLOADING_FILE_TO'},{
ip: (storage_provider==='IP' ? 'IPFS' + formatMessage({id: 'PERMAPINNING_ARWEAVE'}):''),
fl: (storage_provider==='FL' ? 'Fleek':''),
ar: (storage_provider==='AR' ? 'Arweave':'')
}));
if (storage_provider == 'IP') { // IPFS
const ipfs = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
});
storage_id0 = ipfsUpload(encryptedString)
} else if (storage_provider == 'FL') { // Fleek
// Do stuff with Scott's Fleek server
const uploadedFile = await fleekStorage.upload({
apiKey: FLEEK_KEY,
apiSecret: FLEEK_SECRET,
key: documentKey,
data: encryptedString,
});
storage_id0 =
'0x' + bs58.decode(uploadedFile.hashV0).slice(2).toString('hex');
} else if (storage_provider == 'AR') { // Arweave
}
docuStorageHash = {
0:storage_id0,
1:storage_id1
}
handleFileHash(docuStorageHash)
handleCreateProgress(1)
indicatorProgress += 1;
handleIndicatorProgress(indicatorProgress);
continueCallback(1, indicatorProgress, docuStorageHash);
return 1;
} catch (err) {
storeNotif(formatMessage({id: 'FILE_UPLOAD_ERROR'}),formatMessage({id: 'FILE_UPLOAD_ERROR_MESSAGE'}, {button: formatMessage({id: "SEND"})} ), 'danger')
close();
return 0;
}
}
}
export const setDocStorageForDocument = async (contract, provider, documentKey, storage_provider, docuStorageHash, indicatorProgress, handleIndicatorProgress, handleCreateProgress, handleSubmitButton, continueCallback, close, formatMessage) => {
// Now that we have the document uploaded, we need to update the contract
try {
const instance = contract.connect(provider.getSigner());
handleSubmitButton(formatMessage({id: 'SVAING_FILE_ADDRESS_TO_BLOCKCHAIN'}))
const storage_address = fromAscii(storage_provider);
let tx1 = await instance.setDocStorageForDocument(documentKey, storage_address, docuStorageHash[0], docuStorageHash[1]);
handleSubmitButton(formatMessage({id: 'WAITING_FOR_CONFIRMATIONS_FROM_NEWWORK'}))
const networkId = (await provider.getNetwork()).chainId;
await tx1.wait(networkId == 1287 ? 2 : 1).then((receipt) => {
// console.log(receipt)
indicatorProgress += 1;
handleIndicatorProgress(indicatorProgress);
handleCreateProgress(2);
continueCallback(2, indicatorProgress, docuStorageHash);
});
} catch(err) {
storeNotif(formatMessage({id: 'DOCUMENT_STORAGE_ERROR'}), formatMessage({id: 'PLEASE_PRESS_SUBMIT_TO_RETRY'}, {error: err.message.toString()}), 'danger')
close();
return 0;
}
}
export const getEncryptedStringFromFile = async (file, password) => {
const reader = new window.FileReader();
return new Promise((resolve, reject) => {
reader.onload = async(fileEvent) => {
const buffer = Buffer(fileEvent.target.result)
const encryptedString = getEncryptedStringFromBuffer(buffer, password)
resolve(encryptedString)
}
reader.onerror = () => {
reject('oops, something went wrong with the file reader.')
}
reader.readAsArrayBuffer(file)
})
}
export const getEncryptedStringFromBuffer = (buffer, password) => {
const encryptedString = CryptoJS.AES.encrypt(
JSON.stringify(buffer),
password
).toString();
return encryptedString
}
const ipfsUpload = async (encryptedFile) => {
const ipfs = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
});
const file = await ipfs.add(encryptedFile);
const hash = file.path;
let res = await fetch(
`https://ipfs2arweave.com/permapin/${hash}`,
{method: 'POST'}
);
if(!res.ok) {
console.log(await res.json())
throw new Error("ipfs2arweave HTTP status: " + res.status);
}
return ('0x' + bs58.decode(hash).slice(2).toString('hex'))
}
const fleekUpload = async (encryptedFile, key) => {
const uploadedFile = await fleekStorage.upload({
apiKey: FLEEK_KEY,
apiSecret: FLEEK_SECRET,
key: key,
data: encryptedFile,
});
return('0x' + bs58.decode(uploadedFile.hashV0).slice(2).toString('hex'))
}
const aggregateFleekUpload = async (documentKey, ethAccount, instance, encryptedFile, encryptedSigFields, encryptedAnnotations) => {
// TODO: Do stuff with Scott's Fleek server
const saltedDocumentMappingKey = await instance.hashSaltedDocumentMappingKey(documentKey);
const saltedAddressMappingKey = await instance.hashSaltedAddressMappingKey(documentKey, ethAccount);
// uploading to Fleek
const storage_id0 = await fleekUpload(encryptedFile, documentKey);
const sig_storage_id0 = await fleekUpload(encryptedSigFields, saltedDocumentMappingKey);
const annot_storage_id0 = await fleekUpload(encryptedAnnotations, saltedAddressMappingKey);
return {
storage_id0: storage_id0,
sig_storage_id0: sig_storage_id0,
annot_storage_id0: annot_storage_id0
}
}
const getFileUploadMessage = (storageProvider, formatMessage) => {
return (
formatMessage(
{id: 'UPLOADING_FILE_TO'},
{
ip: (storageProvider==='IP' ? 'IPFS' + formatMessage({id: 'PERMAPINNING_ARWEAVE'}):''),
fl: (storageProvider==='FL' ? 'Fleek':''),
ar: (storageProvider==='AR' ? 'Arweave':'')
}
)
)
}
const emptyHex = '0x0000000000000000000000000000000000000000000000000000000000000000';
export const setDocumentCommentsForSigner = async (provider, contract, ethAccount, storageProvider, documentKey, indices, ec_signatures, encryptedAnnotations, formatMessage, callback, errorCallback, handleSaveStatus, signaturesLength) => {
handleSaveStatus(getFileUploadMessage(storageProvider, formatMessage));
const signer = await provider.getSigner(); // signer == current logged in user
const instance = contract.connect(signer);
let annot_storage_id0 = emptyHex
let annot_storage_id1 = emptyHex
const saltedAddressMappingKey = await instance.hashSaltedAddressMappingKey(documentKey, ethAccount);
try {
if (storageProvider == 'IP') { // IPFS
try {
// uploading to IPFS
annot_storage_id0 = await ipfsUpload(encryptedAnnotations)
} catch(err) {
console.log(err);
storageProvider = 'FL';
handleSaveStatus(getFileUploadMessage(storageProvider, formatMessage));
annot_storage_id0 = await fleekUpload(encryptedAnnotations, saltedAddressMappingKey)
}
}
else if (storageProvider == 'FL') { // Fleek
annot_storage_id0 = await fleekUpload(encryptedAnnotations, saltedAddressMappingKey)
} else if (storageProvider == 'AR') { // Arweave
}
storageProvider = fromAscii(storageProvider);
let tx = await instance.aggregateSetSigFieldsAndCommentsAsSigner(documentKey, indices, ec_signatures, [storageProvider, annot_storage_id0, annot_storage_id1], { gasLimit: 2500000 });
// handleSubmitButton(formatMessage({id: 'WAITING_FOR_CONFIRMATIONS_FROM_NEWWORK'}));
handleSaveStatus(formatMessage({id: 'WAITING_FOR_CONFIRMATIONS_FROM_NEWWORK'}))
const networkId = (await provider.getNetwork()).chainId;
await tx.wait(networkId == 1287 ? 2 : 1).then( async (receipt) => {
const message = signaturesLength>0 ? 'SIGNATURES_COMMENTS_SAVED':'COMMENTS_SAVED';
handleSaveStatus(null)
storeNotif(formatMessage({id: 'SUCCESSFUL_TRANSACTION'}), formatMessage({id: message}), 'success')
if(callback){
await callback();
}
});
} catch (err) {
console.log(err)
handleSaveStatus(null)
storeNotif(formatMessage({id: 'FILE_UPLOAD_ERROR'}),formatMessage({id: 'FILE_UPLOAD_ERROR_MESSAGE'}, {button: formatMessage({id: "SIGN"})} ), 'danger')
if(errorCallback){
errorCallback();
}
return false;
}
return true;
}
export const docUpload = async (formatMessage, encryptedFile, contract, handleSubmitButton, storage_provider) => {
handleSubmitButton(getFileUploadMessage(storage_provider, formatMessage));
const documentKey = await getNewDocumentKey(contract);
let hash = emptyHex;
try {
hash = await ipfsUpload(encryptedFile)
} catch(err) {
console.log(err);
storage_provider = "FL"
handleSubmitButton(getFileUploadMessage('FL', formatMessage));
hash = await fleekUpload(encryptedFile, documentKey);
}
return({documentKey, hash, storage_provider})
}
export const aggregateHandleDocumentUpload = async (provider, contract, storageProvider, handleStorageProvider, handleSubmitButton, documentKey, encryptedFile, handleStorageHash, encryptedSigFields, encryptedAnnotations, handleCreateProgress, indicatorProgress, handleIndicatorProgress, continueCallback, close, formatMessage) => {
const signer = await provider.getSigner(); // signer == current logged in user
const ethAccount = await signer.getAddress();
const instance = contract.connect(signer);
// Part 1 and part 2 if cannot fit in bytes32
let storage_id0 = emptyHex
let storage_id1 = emptyHex
let sig_storage_id0 = emptyHex
let sig_storage_id1 = emptyHex
let annot_storage_id0 = emptyHex
let annot_storage_id1 = emptyHex
try {
handleSubmitButton(getFileUploadMessage(storageProvider, formatMessage));
if (storageProvider == 'IP') { // IPFS
try {
// uploading to IPFS
storage_id0 = await ipfsUpload(encryptedFile)
sig_storage_id0 = await ipfsUpload(encryptedSigFields)
annot_storage_id0 = await ipfsUpload(encryptedAnnotations)
} catch(err) {
console.log(err);
storageProvider = 'FL';
handleStorageProvider(storageProvider);
handleSubmitButton(getFileUploadMessage(storageProvider, formatMessage));
const temp = await aggregateFleekUpload(documentKey, ethAccount, instance, encryptedFile, encryptedSigFields, encryptedAnnotations)
storage_id0 = temp.storage_id0
sig_storage_id0 = temp.sig_storage_id0
annot_storage_id0 = temp.annot_storage_id0
}
}
else if (storageProvider == 'FL') { // Fleek
const temp = await aggregateFleekUpload(documentKey, ethAccount, instance, encryptedFile, encryptedSigFields, encryptedAnnotations)
storage_id0 = temp.storage_id0
sig_storage_id0 = temp.sig_storage_id0
annot_storage_id0 = temp.annot_storage_id0
} else if (storageProvider == 'AR') { // Arweave
}
const storage_ids = [storage_id0, storage_id1, sig_storage_id0, sig_storage_id1, annot_storage_id0, annot_storage_id1 ]
handleStorageHash(storage_ids)
handleCreateProgress(1)
indicatorProgress += 1;
handleIndicatorProgress(indicatorProgress);
continueCallback(1, indicatorProgress, storage_ids, storageProvider);
return 1;
} catch (err) {
storeNotif(formatMessage({id: 'FILE_UPLOAD_ERROR'}),formatMessage({id: 'FILE_UPLOAD_ERROR_MESSAGE'}, {button: formatMessage({id: "SEND"})} ), 'danger')
close();
return 0;
}
}
export const aggregateNewBasicDocumentAndSetStorage = async (provider, contract, docKey, docName, expiration, storage_provider, docuStorageHash, signers, numOfSigFields, handleSubmitButton, formatMessage, successTxCallback, failTxCallback, buttonLabel) => {
const instance = contract.connect(provider.getSigner());
const storage_address = fromAscii(storage_provider);
const documentName = fromAscii(docName);
try {
handleSubmitButton(formatMessage({id: "SVAING_FILE_ADDRESS_TO_BLOCKCHAIN"}))
let tx = await instance.aggregateNewBasicDocumentAndSetStorage(docKey, documentName, expiration, signers.length, [storage_address, storage_address, storage_address], docuStorageHash, signers, numOfSigFields, { gasLimit: 2500000 });
handleSubmitButton(formatMessage({id: 'WAITING_FOR_CONFIRMATIONS_FROM_NEWWORK'}));
if(successTxCallback){
await successTxCallback(tx);
}
return 1;
} catch (err) {
console.log(err);
storeNotif(formatMessage({id: 'TRANSACTION_ERROR'}), formatMessage({id: 'PLEASE_PRESS_SUBMIT_TO_RETRY'}, {button: buttonLabel}), 'danger')
if(failTxCallback){
failTxCallback();
}
return 0;
}
}
export const autoSelfSign = async (contract, provider, docKey, createProgress, handleCreateProgress, indicatorProgress, handleIndicatorProgress, handleSubmitButton, continueCallback, close, formatMessage) => {
handleSubmitButton(formatMessage({id: 'AUTOMATICLLY_SELF_SIGNING'}))
let basicDoc = await getDocument(docKey, contract, provider, formatMessage);
let fullDoc = await getDocumentDetails(docKey, basicDoc, contract, provider, formatMessage);
try {
if(await signDocumentAtIndex(contract, provider, docKey, fullDoc.ipfsHash, fullDoc.metaHash, 0, handleSubmitButton, formatMessage)) {
indicatorProgress += 1;
handleIndicatorProgress(indicatorProgress);
handleCreateProgress(3);
continueCallback(3, indicatorProgress);
} else {
storeNotif(formatMessage({id: 'TRANSACTION_ERROR'}), formatMessage({id: 'ERROR_SIGNING_DOCUMENT'}), 'danger');
close();
return 0;
}
} catch(err) {
storeNotif(formatMessage({id: 'TRANSACTION_ERROR'}), formatMessage({id: 'PLEASE_PRESS_SUBMIT_TO_RETRY'}, {button: formatMessage({id: 'SEND'}) }), 'danger')
close();
return 0;
}
return 1;
}
// Upload document metadata file
export const handleDocumentMetadataUpload = async (instance, docKey, file, storage_provider) => {
// Literally the same as uploading handleDocumentUpload() except for the smart contract calls below:
// const saltedDocumentMappingKey = await instance.hashSaltedDocumentMappingKey(docKey);
// await instance.setMetaStorageForDocument(docKey, saltedDocumentMappingKey, storage_provider, storage_id0, storage_id1);
}
const readFile = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
reader.readAsArrayBuffer(file);
});
}
const getNumPages = async (file) => {
const arrayBuffer = await readFile(file);
const pdf = await PDFDocument.load(arrayBuffer);
return pdf.getPages().length;
}
export const validateUploadFile = async (file, handlePageCount, handleFile, handleFilename, handleOriginalFilename, formatMessage) => {
if (file.type != "application/pdf") {
storeNotif(formatMessage({id: 'FILE_TYPE_ERROR'}), formatMessage({id: 'ONLY_PROCESS_PDFS'}), 'danger')
}else{
let tempFile = file;
try {
let count = await getNumPages(tempFile)
// await reader.result.match(/\/Type[\]*\/Page[^s]/i).length;
handlePageCount(count)
handleOriginalFilename(tempFile.name);
handleFile(tempFile);
let name = getFileName(file)
// PDF name length error checker
if(tempFile.name.length >= 32) {
name.name = name.name.slice(0, 31 - name.ext.length);
// Our file name can only be 32 characters long WITH NULL TERMINATOR, including extension. Do calculation below and slice it.
storeNotif(formatMessage({id: 'FILE_RENAMED'}), `${formatMessage({id: "FILENAME_HAS_BEEN_SHORTENED"})} ${name.name}`, 'info')
}
handleFilename(name)
} catch(error) {
// Invalid PDF, no page count included
handlePageCount(null);
handleFile(null);
storeNotif(formatMessage({id: 'INVALID_PDF'}), formatMessage({id: 'PLEASE_TRY_ANOTHER_FILE'}), 'danger')
return;
}
}
}
export default { autoSelfSign, aggregateHandleDocumentUpload, aggregateNewBasicDocumentAndSetStorage, getEncryptedStringFromFile}