UNPKG

@foreverrbum/ethsign

Version:

This package will allow you to electronically sign documents within your application

428 lines (377 loc) 18.8 kB
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}