Skip to main content
Version: v3.x.x


Encrypting on-chain data (server-side)

Learn how to encrypt data before storing on-chain on Irys.


At completion of this reading you should be able to:

  • Encrypt data using Lit Protocol.
  • Establish a set of rules determining who can decrypt the data.
  • Store encrypted data on Arweave using Irys.
  • Decrypt data using Lit Protocol.

What is Irys?

Irys is a provenance layer that enables users to scale permanent data and precisely attribute its origin. By tracing and verifying where data comes from, Irys paves the way to incorporate accountability into all information.

Data uploaded to Irys is stored permanently on Arweave. Once on Arweave, this data becomes publicly accessible, anyone can view it. For projects where privacy is a concern, you can use Lit to encrypt your data before storing it on Irys.

All of the code from this guide is also contained in GitHub repository.


To follow along with this guide, you will need to install the following using npm:

npm install @irys/sdk @lit-protocol/lit-node-client-nodejs@^3 dotenv ethers@^5 siwe@^2.1.4

or yarn:

yarn add @irys/sdk @lit-protocol/lit-node-client-nodejs@^3 dotenv ethers@^5 siwe@^2.1.4


To run the code in this project, you'll need to import the following:

import * as LitJsSdk from "@lit-protocol/lit-node-client-nodejs";
import Irys from "@irys/sdk";
import ethers from "ethers";
import siwe from "siwe";
import dotenv from "dotenv";

Encrypting data

Encrypting data with Irys and Lit

There are three steps to encrypting data

  • Obtain a wallet signature (authSig), which proves you own a wallet
  • Define access control conditions for who can decrypt your data
  • Connect to a Lit node and request that it encrypt your data

Lit Protocol supports both wallet signatures and session signatures. This guide focuses solely on wallet signatures, as session signatures are currently in development and only available for Ethereum.

Wallet signature

A wallet signature (authSig) demonstrates true ownership of a wallet. By signing a basic transaction, regardless of its contents, you verify access to the wallet.

First, create a file called .env with a single value, and include your private key.


Then, create a helper function that creates a message and signs it using your private key.

async function getAuthSig() {
// Initialize the signer
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
const address = ethers.utils.getAddress(await wallet.getAddress());

// Craft the SIWE message
const domain = "localhost";
const origin = "https://localhost/login";
const statement =
"This is a test statement. You can put anything you want here.";
const siweMessage = new siwe.SiweMessage({
address: address,
uri: origin,
version: "1",
chainId: "1",
const messageToSign = siweMessage.prepareMessage();

// Sign the message and format the authSig
const signature = await wallet.signMessage(messageToSign);
const authSig = {
sig: signature,
derivedVia: "web3.eth.personal.sign",
signedMessage: messageToSign,
address: address,

return authSig;

Access control conditions

Lit Protocol enables users to set access control conditions specifying who can decrypt data. This provides builders with the flexibility to designate data decryption permissions, including:

  • A single wallet address
  • DAO membership
  • Owners of an ERC20 or ERC721
  • Outcomes from a smart contract call
  • Outcomes from an API call

To ensure anyone can run the code in this repository, it uses the following for access control, allowing anyone with an ETH balance >= 0 to decrypt. More details on the different types of access control conditions supported.

// 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;

Using Lit, the access control conditions provide near infinite flexibility. Imagine a system for government bid management: bids are required to be submitted by a specific deadline, tracked using Irys' millisecond-accurate timestamps. The bids remain encrypted up to this deadline, aiding in preventing corruption by ensuring the bids are inaccessible to all parties until the designated time.

Connecting to a Lit node

Write a helper function to connect to a Lit node:

async function getLitNodeClient() {
// Initialize LitNodeClient
const litNodeClient = new LitJsSdk.LitNodeClientNodeJs({
alertWhenUnauthorized: false,
litNetwork: "cayenne",
await litNodeClient.connect();

return litNodeClient;

Encrypt data

Finally, write a function that accepts a string and uses the code we wrote earlier to encrypt it. In this guide we're using the Lit function encryptString() which encrypts a string and returns both the encrypted string and a hash of the original string. Lit also hasencryptFile() for encrypting files directly.

async function encryptData(dataToEncrypt) {
const authSig = await getAuthSig();
const accessControlConditions = getAccessControlConditions();
const litNodeClient = await getLitNodeClient();

// 1. Encryption
// <Blob> encryptedString
// <Uint8Array(32)> dataToEncryptHash
const { ciphertext, dataToEncryptHash } = await LitJsSdk.encryptString(
dataToEncrypt: dataToEncrypt,
chain: "ethereum",
return [ciphertext, dataToEncryptHash];

Storing on Arweave via Irys

To use Irys to store data on Arweave, first connect to an Irys node. This function uses the same private key from our .env file and connects to the Irys Devnet where uploads are stored for 60 days. In a production environment, you would change this to use Irys' Node 1 or 2 where uploads are permanent.


This code is configured to MATIC to pay for uploads, and while working with the Irys Devnet, you need to fund your wallet with free MUMBAI MATIC Devnet tokens. Alternatively, you could use any other Devnet token supported by Irys.

async function getIrys() {
const url = "";
const providerUrl = "";
const token = "matic";

const irys = new Irys({
url, // URL of the node you want to connect to
token, // Token used for payment
key: process.env.PRIVATE_KEY, // Private key
config: { providerUrl }, // Optional provider URL, only required when using Devnet
return irys;

Then write a function that takes the encrypted data, the original data hash, the access control conditions, and stores it all on Arweave using Irys.

Irys' upload function returns a signed receipt containing the exact time (in milliseconds) of the upload and also a transaction ID, which can then be used to download the data from a gateway.


For simplicity, we'll consolidate all three values into a JSON object and upload it to Irys in one transaction. This is a design choice; you have the flexibility to store these values as you see fit in your own implementation.

async function storeOnIrys(cipherText, dataToEncryptHash) {
const irys = await getIrys();

const dataToUpload = {
cipherText: cipherText,
dataToEncryptHash: dataToEncryptHash,
accessControlConditions: getAccessControlConditions(),

let receipt;
try {
const tags = [{ name: "Content-Type", value: "application/json" }];
receipt = await irys.upload(JSON.stringify(dataToUpload), { tags });
} catch (e) {
console.log("Error uploading data ", e);

return receipt?.id;

Decrypting data

Decrypting data with Irys and Lit

There are three steps to decrypting data:

  • Obtain a wallet signature (AuthSig), which proves you own a wallet
  • Retrieve data stored on Arweave
  • Connect to a Lit node and request that it decrypt your data

Retrieving data from Arweve using the Irys gatway

To download data stored on Arweave, the easiest way is to connect to a gateway and request the data using your transaction ID. In this example, we'll use the Irys gateway.

This function downloads the data JSON object, parses out the three values and returns them as an array of strings.

async function retrieveFromIrys(id) {
const gatewayAddress = "";
const url = `${gatewayAddress}${id}`;

try {
const response = await fetch(url);

if (!response.ok) {
throw new Error(`Failed to retrieve data for ID: ${id}`);

const data = await response.json();
return [
} catch (e) {
console.log("Error retrieving data ", e);

Decrypting data

Finally, we decrypt the data using Lit's decryptString() function.

async function decryptData(
) {
const authSig = await getAuthSig();
const litNodeClient = await getLitNodeClient();

let decryptedString;
try {
decryptedString = await LitJsSdk.decryptToString(
chain: "ethereum",
} catch (e) {

return decryptedString;

Main function

Finally, write a main() function that calls the calls our encrypt, store and decrypt code.

async function main() {
const messageToEncrypt = "Irys + Lit is 🔥x2";

// 1. Encrypt data
const [cipherText, dataToEncryptHash] = await encryptData(messageToEncrypt);

// 2. Store cipherText and dataToEncryptHash on Irys
const encryptedDataID = await storeOnIrys(cipherText, dataToEncryptHash);

console.log(`Data stored at${encryptedDataID}`);

// 3. Retrieve data stored on Irys
// In real world applications, you could wait any amount of time before retrieving and decrypting
const [
] = await retrieveFromIrys(encryptedDataID);
// 4. Decrypt data
const decryptedString = await decryptData(
console.log("decryptedString:", decryptedString);


Getting support

If you have questions while building, make sure to reach out to the Lit development team on Discord.

Questions about Irys? Go to the Irys Discord to get in touch.