Working with Access Control
How to set up access control through Lit via encryption, JWT or programmatic signing
Content stored on the decentralized web is platformless and public. This introduces privacy concerns as we now have a fully accessible online world. Since this content is stored publicly, we need a way to keep information private. How do we keep publicly searchable content private? Through encryption!
NFTs are an example of decentralized public content.
NFTs enable platformless media— where media isn’t tied to any one single entity. While marketplaces and platforms will exist, users will be given the optionality of where to take their NFTs. - Kinjal Shah, When Consumer Meets Crypto
Non-fungible tokens (NFTs) are amazing because they are not bound to any platform. They are platformless because they exist on a decentralized ledger. This creates interesting possibilities around ownership and access. In the decentralized web, interactions are captured as publicly available data associated with a blockchain address.
By making data portability virtually frictionless, Web3 has provided the technological foundation to chip away at the moats many large internet businesses rely on today. - Reed McGinley Stempel, Web3 and the Future of Data Portability
We can think about this encrypted information as unlockable content. Through unlockable content, NFTs and on-chain data become dynamic assets that are keys to exclusive experiences. What are some examples?
Token gated access to a DAO
Physical copy or print of an artwork
Off-chain link to a Google Drive, Dropbox, or other databases
WAV stems for a song
Currently, there are a few platforms that support NFTs with unlockable content.
Using Lit, it's possible to provision decryption keys so any content can be decentralized and private, essentially encrypted and only accessible to those with the specified access control. Imagine a future where an NFT that is unlockable on OpenSea is unlockable on SolSea. Additionally, Lit makes it possible to have private content accessible outside of traditional NFT platforms. An example is token gating items in a WordPress store.
Decentralize Unlockable Content With Lit Protocol
Lit is a decentralized network that is capable of provisioning a blockchain user access to resources based on on-chain data, like an NFT.
Lit can:
Encrypt and lock static content (images, videos, music, etc) behind on-chain conditions.
Decrypt static content that was locked behind an on-chain condition.
Authorize network signatures that provide access to dynamic content (for example, a server or network resource) behind an on-chain condition.
Request a network signed JWT that provisions access and authorization to dynamic content behind an on-chain condition.
To make it easier for developers to bring encryption into their applications Lit has a JavaScript SDK. Check out the documentation for more information.
To learn more about access control here are a few examples to try out:
IPFS x Lit enabled dApp
You can fork this branch of the project and follow along from the beginning of the readme which contains a guide or jump to checking out the complete code with Lit here. On a high level, the guide covers how to set up access control conditions and create encryption and decryption calls to the Lit nodes. Within this post, we’ll cover the format of an access control condition and the stubs for encrypting and decrypting data.
The DApp connects to IPFS (InterPlanetary File System) to upload files, stores the Lit encrypted IPFS content identifier (CID) as a string to the application’s state, and decrypts the CID to display the uploaded file. The access control conditions for encrypting and decrypting the file will be whether or not the connected wallet holds a Monster Suit NFT.
IPFS is a communication protocol that uses peer-to-peer networking to store, retrieve, and share data through a distributed file system mechanism.
On IFPS, when you upload data to an existing node on the protocol, the data is distributed into smaller chunks, then hashed and given a unique content identifier (CID). For every new upload of new data or previously uploaded data, a new cryptographic hash (CID) is generated, making every upload to the network unique.
Access control
On an ERC-721 NFT collection, we can gate access to encryption and decryption based on the following access control condition, which states a wallet must hold at least one Monster Suit NFT:
// Must hold at least one Monster Suit NFT ([<https://opensea.io/collection/monster-suit>](<https://opensea.io/collection/monster-suit>))
const accessControlConditionsNFT = [
{
contractAddress: '0x89b597199dAc806Ceecfc091e56044D34E59985c',
standardContractType: 'ERC721',
chain,
method: 'balanceOf',
parameters: [
':userAddress'
],
returnValueTest: {
comparator: '>',
value: '0'
}
}
];
Check out the access control documentation to see additional use cases such as access control lists through smart contracts and using boolean operations. To check if your schema matches what the Lit nodes expect, use the access control conditions debugger tool.
Encryption
When encrypting through Lit, you’ll need to use the following functions provided by the Lit JS SDK.
checkAndSignAuthMessage: Checks for an existing cryptographic authentication signature and creates one if it doesn’t exist. This is used to prove ownership of a given wallet address to the Lit nodes.
encryptString: Encrypts any string.
saveEncryptionKey: Securely saves the association between access control conditions and the content we want to decrypt. This is saved to the Lit Nodes.
uint8arrayToString: Helper function that converts a Uint8Array to a string.
An example encryption function:
const chain = 'ethereum';
async encryptString(str) {
if (!this.litNodeClient) {
await this.connect();
}
}
const authSig = await LitJsSdk.checkAndSignAuthMessage({ chain });
const { encryptedString, symmetricKey } = await LitJsSdk.encryptString(str);
const encryptedSymmetricKey = await this.litNodeClient.saveEncryptionKey({
accessControlConditions: accessControlConditionsNFT,
symmetricKey,
authSig,
chain,
});
return {
encryptedString,
encryptedSymmetricKey: LitJsSdk.uint8arrayToString(encryptedSymmetricKey, "base16")
};
}
Decryption
When decryption Lit encrypted data, you’ll need to use the following functions provided by the Lit JS SDK.
checkAndSignAuthMessage: Checks for an existing cryptographic authentication signature and creates one if it doesn’t exist. This is used to prove ownership of a given wallet address to the Lit nodes.
getEncryptionKey: Retrieves the symmetric encryption key from the LIT nodes. Note that this will only work if the current wallet meets the access control conditions specified when the data was encrypted.
decryptString: Decrypt a string that was encrypted using the Lit Node Client encryptString function.
An example decryption function
async decryptString(encryptedStr, encryptedSymmetricKey) {
if (!this.litNodeClient) {
await this.connect()
}
const authSig = await LitJsSdk.checkAndSignAuthMessage({ chain })
const symmetricKey = await this.litNodeClient.getEncryptionKey({
accessControlConditions: accessControlConditionsNFT,
toDecrypt: encryptedSymmetricKey,
chain,
authSig
})
const decryptedString = await LitJsSdk.decryptString(
encryptedStr,
symmetricKey
);
return { decryptedString }
}
These are the basics for access control and encryption using the Lit JS SDK. For more advanced examples and use cases, check out the resources here.
Basic NextJS x Lit Token Gated Page Replit template
Want to understand how to token gate a page using NextJS, JWT and Lit?
Fork this example and test out different access control conditions that gate a /protected
page.
To run within Replit, go to the shell in the bottom right and run the following commands:
npm install
npm run dev
The access control conditions set on the code are for folks who hold at least 400 Developer DAO tokens.
const accessControlConditions = [
{
contractAddress: '0xb24cd494faE4C180A89975F1328Eab2a7D5d8f11',
standardContractType: 'ERC20',
chain: 'ethereum',
method: 'balanceOf',
parameters: [
':userAddress'
],
returnValueTest: {
comparator: '>=',
value: '400'
}
}
]
Set up the resourceId of the page that will be token gated, in the case of this project - we’re creating the path to the access controlled page as /protected
.
const resourceId = {
baseUrl: '<http://localhost:3000>',
path: '/protected',
orgId: "",
role: "",
extraData: id
}
Obtain a JWT from the Lit nodes based on whether or not the wallet used meets the access control conditions.
// Connect to the Lit Node client
await client.connect()
const authSig = await LitJsSdk.checkAndSignAuthMessage({ chain: 'ethereum' })
await client.saveSigningCondition({ accessControlConditions, chain: 'ethereum', authSig, resourceId })
try {
const jwt = await client.getSignedToken({
accessControlConditions, chain: 'ethereum', authSig, resourceId: resourceId
})
What’s next? Programmatic Signing!
Continue exploring the functionalities in Lit by building unlockable content with programmatic signing! At Lit, we’re building out a new paradigm in decentralized cryptography to read and write data between blockchains and off-chain platforms, facilitating encryption, access control, and automation for the open web through programmatic signing.
Lit’s access control protocol gives individuals the ability to read private data from the dWeb based on on-chain conditions. But this is only one half of the equation. What about writing data?
To facilitate this second use case we created two things: Lit Actions and Programmable Key Pairs (PKPs). Lit Actions are immutable JavaScript functions stored on IPFS. Actions can be thought of as smart contracts with superpowers: they have network access and the ability to make arbitrary HTTP requests.
PKPs are the public/private key pairs generated by the Lit network and are minted in the form of an ERC-721 NFT. The owner of the NFT becomes the sole controller of the underlying private key.
When these components work together, they have the power to facilitate complex condition-based automation.
Lit Actions inherit the powerful condition checking that Lit Protocol utilizes for Access Control. You can easily check any on-chain condition inside a Lit Action. Let’s take a look at setting access control through a Lit Action and only signing once that access control condition is met.
Lit Actions
To create a Lit Action, you need to write some Javascript code that will accomplish your goals. The Lit Protocol provides JS function bindings to do things like request a signature or a decryption.
You'll also need some client side JS to collect the responses from the Lit Nodes and combine them above the threshold into a signature or decrypted data.
This Lit Action will check the access control condition and if the condition is met sign the string "Hello World" with the shared testnet ECDSA key and return the signature.
// this code will be run on the Lit nodes
const litActionCode = `
const go = async () => {
// test an access control condition
const testResult = await LitActions.checkConditions({conditions, authSig, chain})
console.log('testResult', testResult)
// only sign if the access condition is true
if (!testResult){
return;
}
// this is the string "Hello World" for testing
const toSign = [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100];
// this requests a signature share from the Lit Node
// the signature share will be automatically returned in the HTTP response from the node
const sigShare = await LitActions.signEcdsa({ toSign, publicKey: "1", sigName: "sig1" });
};
go();
`;
Below is the function that passes in the access control condition and runs the Lit Action. Check out the conditions
parameter, which is checking that someone has a positive non-zero amount of ethereum.
const runLitAction = async () => {
const litNodeClient = new LitJsSdk.LitNodeClient({
litNetwork: "serrano",
});
await litNodeClient.connect();
const signatures = await litNodeClient.executeJs({
code: litActionCode,
authSig,
jsParams: {
conditions: [
{
conditionType: "evmBasic",
contractAddress: "",
standardContractType: "",
chain: "ethereum",
method: "eth_getBalance",
parameters: [":userAddress", "latest"],
returnValueTest: {
comparator: ">=",
value: "0",
},
},
],
authSig: {
sig: "0x2bdede6164f56a601fc17a8a78327d28b54e87cf3fa20373fca1d73b804566736d76efe2dd79a4627870a50e66e1a9050ca333b6f98d9415d8bca424980611ca1c",
derivedVia: "web3.eth.personal.sign",
signedMessage:
"localhost wants you to sign in with your Ethereum account:\\n0x9D1a5EC58232A894eBFcB5e466E3075b23101B89\\n\\n",
address: "0x9D1a5EC58232A894eBFcB5e466E3075b23101B89",
},
chain: "ethereum",
},
});
console.log("signatures: ", signatures);
};
runLitAction();
This is a basic example that showcases being able to use an access control condition within a Lit Action. An intermediate example might be something like an access control condition check and a call to a web2 endpoint - enabling on-chain access control with web2 data.
Learn more about writing Lit Actions here.
See you around the ecosystem!
- Deb
Inspired to build? Follow up with the Lit team and participate in the ecosystem.
💻 Developer Documentation: https://developer.litprotocol.com/
👾 Official Discord: https://litgateway.com/discord
🐙 GitHub: https://github.com/LIT-Protocol
🕊 Twitter: https://twitter.com/LitProtocol
🖥 Lit Website: https://litprotocol.com/