Encryption
Encrypting data in the browser

Encrypting onchain data (browser-based)

This guide teaches you how to:

  • Encrypt a File (opens in a new tab) uploaded from the browser using Lit Protocol
  • Establish a set of rules determining who can decrypt the File
  • Store the encrypted File on Arweave using Irys
  • Decrypt the File using Lit Protocol
  • Display the decrypted File in the browser

Before diving into this guide, begin with "Encrypting onchain data".

Dependencies

Install using npm:

npm install @irys/sdk @lit-protocol/lit-node-client@^3 ethers@^5

or yarn:

yarn add @irys/sdk @lit-protocol/lit-node-client@^3 ethers@^5

Imports

import * as LitJsSdk from "@lit-protocol/lit-node-client";
import { AccessControlConditions, ILitNodeClient } from "@lit-protocol/types";
import { checkAndSignAuthMessage } from "@lit-protocol/lit-node-client";
import { WebIrys } from "@irys/sdk";

Encrypting a File

File uploader

Add a form to an HTML page that accepts a file as input:

<form id="uploadForm">
	<input type="file" name="fileToUpload" id="fileToUpload" accept="*/*" required />
	<input type="button" value="Upload File" onclick="handleUpload()" />
</form>

Wallet signature

Use the Lit SDK function checkAndSignAuthMessage() (opens in a new tab) to prompt the user to sign a basic transaction, confirming wallet ownership. Authentication details are then saved in the browser's local storage, future calls to checkAndSignAuthMessage() will use the stored version if present.

const authSig = await checkAndSignAuthMessage({
	chain: process.env.NEXT_PUBLIC_LIT_CHAIN || "polygon",
});

Lit node

Connect to a Lit node:

const litNodeClient = new LitJsSdk.LitNodeClient({
	litNetwork: "cayenne",
});
await litNodeClient.connect();

Access control conditions

Define rules for who to decrypt your data (opens in a new tab), limiting it to anyone with >= 0 ETH:

// This defines who can decrypt the data
function getAccessControlConditions() {
	const accessControlConditions = [
		{
			contractAddress: "",
			standardContractType: "",
			chain: "ethereum",
			method: "eth_getBalance",
			parameters: [":userAddress", "latest"],
			returnValueTest: {
				comparator: ">=",
				value: "0", // 0 ETH, so anyone can open
			},
		},
	];
 
	return accessControlConditions;
}

To the wallet 0x50e2dac5e78B5905CB09495547452cEE64426db2

const accessControlConditions = [
	{
		contractAddress: "",
		standardContractType: "",
		chain,
		method: "",
		parameters: [":userAddress"],
		returnValueTest: {
			comparator: "=",
			value: "0x50e2dac5e78B5905CB09495547452cEE64426db2",
		},
	},
];

Or by people who hold a given ERC721:

const accessControlConditions = [
	{
		contractAddress: "0xA80617371A5f511Bf4c1dDf822E6040acaa63e71",
		standardContractType: "ERC721",
		chain,
		method: "balanceOf",
		parameters: [":userAddress"],
		returnValueTest: {
			comparator: ">",
			value: "0",
		},
	},
];

Encrypting

Finally, encrypt the File using Lit's encryptFileAndZipWithMetadata() (opens in a new tab) function.

This function takes the File object, encrypts it and stores it in a single zip file with all metadata.

// Create a zip blob containing the encrypted file and associated metadata
const zipBlob = await LitJsSdk.encryptFileAndZipWithMetadata({
	chain: process.env.NEXT_PUBLIC_LIT_CHAIN || "polygon",
	authSig,
	accessControlConditions,
	file,
	litNodeClient,
	readme: "This file was encrypted using LitProtocol and the Irys Provenance Toolkit.",
});

Storing on Arweave via Irys

Once encrypted, use webIrys.uploadFile() to upload the zip blob. In this case, we tag the upload with a tag indicating the content type of the underlying file, and another tag letting us know it's encrypted.

// Tag the upload marking it as
// - Binary file
// - Containing a file of type file.type (used when displaying)
// - Encrypted (used by our display code)
const tags: Tag[] = [
	{
		name: "Content-Type",
		value: "application/octet-stream",
	},
	{
		name: "Encrypted-File-Content-Type",
		value: file.type,
	},
	{
		name: "Irys-Encrypted",
		value: "true",
	},
];
 
const receipt = await irys.uploadFile(file, {
	tags,
});

Combined file

Then, combine everything in a single file:

async function encryptFile(file: File) {
	// 1. Connect to a Lit node
	const litNodeClient = new LitJsSdk.LitNodeClient({
		litNetwork: "cayenne",
	});
	await litNodeClient.connect();
 
	// 2. Ensure we have a wallet signature
	const authSig = await checkAndSignAuthMessage({
		chain: "ethereum",
		nonce: await litNodeClient.getLatestBlockhash(),
	});
 
	// 3. Define access control conditions.
	// This defines who can decrypt, current settings allow for
	// anyone with a ETH balance >= 0 to decrypt, which
	// means that anyone can. This is for demo purposes.
	const accessControlConditions = [
		{
			contractAddress: "",
			standardContractType: "",
			chain: "ethereum",
			method: "eth_getBalance",
			parameters: [":userAddress", "latest"],
			returnValueTest: {
				comparator: ">=",
				value: "0",
			},
		},
	];
 
	// 4. Create a zip blob containing the encrypted file and associated metadata
	const zipBlob = await LitJsSdk.encryptFileAndZipWithMetadata({
		chain: process.env.NEXT_PUBLIC_LIT_CHAIN || "ethereum",
		authSig,
		accessControlConditions,
		file,
		litNodeClient,
		readme: "This file was encrypted using LitProtocol and the Irys Provenance Toolkit.",
	});
 
	return zipBlob;
}
 
// Uploads the encrypted File (with metadata) to Irys
async function uploadFile(file: File): Promise<string> {
	const irys = await getIrys();
 
	try {
		const price = await irys.getPrice(file?.size);
		const balance = await irys.getLoadedBalance();
 
		if (price.isGreaterThanOrEqualTo(balance)) {
			console.log("Funding node.");
			await irys.fund(price);
		} else {
			console.log("Funding not needed, balance sufficient.");
		}
 
		// Tag the upload marking it as
		// - Binary file
		// - Containing a file of type file.type (used when displaying)
		// - Encrypted (used by our display code)
		const tags: Tag[] = [
			{
				name: "Content-Type",
				value: "application/octet-stream",
			},
			{
				name: "Encrypted-File-Content-Type",
				value: file.type,
			},
			{
				name: "Irys-Encrypted",
				value: "true",
			},
		];
 
		const receipt = await irys.uploadFile(file, {
			tags,
		});
		console.log(`Uploaded successfully. ${GATEWAY_BASE}${receipt.id}`);
 
		return receipt.id;
	} catch (e) {
		console.log("Error uploading single file ", e);
	}
	return "";
}
 
// Encrypts and then uploads a File
async function encryptAndUploadFile(file: File): Promise<string> {
	const encryptedData = await encryptFile(file);
	return await uploadFile(encryptedData);
}

And call it from your HTML form:

<form id="uploadForm">
	<input type="file" name="fileToUpload" id="fileToUpload" accept="*/*" required />
	<input type="button" value="Upload File" onclick="handleUpload()" />
</form>
 
<script>
	document.getElementById("uploadForm").addEventListener("submit", function (event) {
		event.preventDefault();
		handleUpload();
	});
 
	async function handleUpload() {
		const fileInput = document.getElementById("fileToUpload");
		const file = fileInput.files[0];
		if (file) {
			const result = await encryptAndUploadFile(file);
		} else {
			alert("Please select a file to upload.");
		}
	}
</script>

Decrypting a File

To decrypt and display the image:

  1. Load the data from the Irys gateway
  2. Extract the zip blob
  3. Decrypt it
async function decryptFile(id: string, encryptedFileType: string): Promise<string> {
	try {
		// 1. Retrieve the file from https://gateway.irys.xyz/${id}
		const response = await fetch(`${GATEWAY_BASE}${id}`);
		if (!response.ok) {
			throw new Error(`Failed to fetch encrypted file from gateway with ID: ${id}`);
		}
 
		// 2. Extract the zipBlob
		const zipBlob = await response.blob();
 
		// 3. Connect to a Lit node
		const litNodeClient = new LitJsSdk.LitNodeClient({
			litNetwork: "cayenne",
		});
		await litNodeClient.connect();
 
		// 3.5 Get a reference to an AuthSig (if in local storage that will be used instead of prompting the user to sign)
		const authSig = await checkAndSignAuthMessage({
			chain: "ethereum",
			nonce: await litNodeClient.getLatestBlockhash(),
		});
 
		// 4. Decrypt the zipBlob
		const result = await LitJsSdk.decryptZipFileWithMetadata({
			file: zipBlob,
			litNodeClient: litNodeClient,
			authSig: authSig, // Include this only if necessary
		});
		const decryptedFile = result.decryptedFile;
		// 5. Convert to a blob
		const blob = arrayBufferToBlob(decryptedFile, encryptedFileType);
		// 6. Build a dynamic URL
		const dataUrl = await blobToDataURL(blob);
 
		return dataUrl;
	} catch (e) {
		console.error("Error decrypting file:", e);
	}
	return "";
}

Displaying encrypted File in the browser

After decrypting the image file, you need to convert the data blob to a URL with the format data:image/png;base64,[base64-encoded-data] before setting it as the src attribute of an <img> element.

These functions assist in converting the data blob to a URL.

// Helper functions for use in showing decrypted images
function arrayBufferToBlob(buffer: ArrayBuffer, type: string): Blob {
	return new Blob([buffer], { type: type });
}
 
function blobToDataURL(blob: Blob): Promise<string> {
	return new Promise((resolve, reject) => {
		const reader = new FileReader();
		reader.onload = (event) => {
			if (event.target?.result) {
				resolve(event.target.result as string);
			} else {
				reject(new Error("Failed to read blob as Data URL"));
			}
		};
		reader.readAsDataURL(blob);
	});
}