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:
- Load the data from the Irys gateway
- Extract the zip blob
- 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);
});
}