Skip to content

Commit 3aa9154

Browse files
Merge pull request #79 from LIT-Protocol/wyatt/hacker-guide
Hacker Guide - Encrypt File
2 parents e5aea56 + 71af0b1 commit 3aa9154

File tree

24 files changed

+23398
-212
lines changed

24 files changed

+23398
-212
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
.env
11+
node_modules
12+
dist
13+
dist-ssr
14+
*.local
15+
16+
# Editor directories and files
17+
.vscode/*
18+
!.vscode/extensions.json
19+
.idea
20+
.DS_Store
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
.env
11+
node_modules
12+
dist
13+
dist-ssr
14+
*.local
15+
16+
# Editor directories and files
17+
.vscode/*
18+
!.vscode/extensions.json
19+
.idea
20+
.DS_Store
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?

hacker-guides/decentralized-serverless-functions/README.md

Whitespace-only changes.

hacker-guides/encryption/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ For more information on how to define and use ACCs, refer to the [Access Control
3232
Now that you have an overview of encrypting data with Lit, you can continue on to the encryption guides:
3333

3434
- [Encrypting a String](./encrypt-string/README.md)
35+
- [Encrypting a File](./encrypt-file/README.md)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ETHEREUM_PRIVATE_KEY=
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
.env
11+
node_modules
12+
dist
13+
dist-ssr
14+
*.local
15+
16+
# Editor directories and files
17+
.vscode/*
18+
!.vscode/extensions.json
19+
.idea
20+
.DS_Store
21+
*.suo
22+
*.ntvs*
23+
*.njsproj
24+
*.sln
25+
*.sw?
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "https://json.schemastore.org/mocharc.json",
3+
"require": "tsx"
4+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<!-- omit in toc -->
2+
# Encrypting a File
3+
4+
After connecting to the Lit Network and authenticating a session, you can encrypt a file using the Lit SDK's [encrypt](https://v6-api-doc-lit-js-sdk.vercel.app/classes/lit_node_client_src.LitNodeClientNodeJs.html#encrypt) method. This method takes in the file you want to encrypt, as well as an array of [Access Control Conditions (ACCs)](https://developer.litprotocol.com/docs/advanced-topics/access-control-conditions) that specify who is authorized to decrypt the data.
5+
6+
Encryption happens client side, as the instance of `LitNodeClient` you created has retrieved the public BLS key for the Lit Network you are connected to.
7+
8+
When you want to decrypt the file, you will submit the _encryption metadata_ (ciphertext, dataToEncryptHash, and access control conditions) and your Session Signatures to the Lit Network. Each Lit node will verify that you have the necessary permissions to decrypt the data (by parsing the access control conditions the data was encrypted with) and then generate their decryption shares if authorized. Once all the decryption shares are generated, they are returned back to the client where the Lit SDK will combine them and return the decrypted file.
9+
10+
- [Prerequisites](#prerequisites)
11+
- [Running the Code Example](#running-the-code-example)
12+
- [Requirements](#requirements)
13+
- [Steps](#steps)
14+
- [Expected Output](#expected-output)
15+
- [Understanding the Code](#understanding-the-code)
16+
- [Reading the Unencrypted File](#reading-the-unencrypted-file)
17+
- [Creating an Ethers signer](#creating-an-ethers-signer)
18+
- [Defining the Access Control Conditions](#defining-the-access-control-conditions)
19+
- [Encrypting the Data](#encrypting-the-data)
20+
- [Requesting Session Signatures](#requesting-session-signatures)
21+
- [Decrypting the Data](#decrypting-the-data)
22+
- [Next Steps](#next-steps)
23+
24+
## Prerequisites
25+
26+
- Understanding of Lit core terminology and concepts covered [here](../README.md#core-terminology)
27+
- Understanding of Lit encryption terminology and concepts covered [here](../README.md#relevant-terminology)
28+
- Understanding of the [Connecting to the Lit Network](../connecting-to-lit/README.md) guide
29+
- Understanding of the [Authenticating a Session](../../_getting-started/authenticating-a-session/README.md) guide
30+
31+
## Running the Code Example
32+
33+
### Requirements
34+
35+
- [Node.js](https://nodejs.org/en)
36+
- [Yarn](https://yarnpkg.com/getting-started)
37+
- `@lit-protocol/constants`
38+
- `@lit-protocol/lit-node-client`
39+
- `@lit-protocol/auth-helpers`
40+
- `@lit-protocol/types`
41+
42+
### Steps
43+
44+
1. `yarn` to install the dependencies
45+
2. `yarn test` to run the code example
46+
47+
### Expected Output
48+
49+
After running the code example, you should see output in your terminal:
50+
51+
1. An indication that a connection to the Lit Network was successfully established
52+
2. The file was successfully encrypted
53+
- The `dataToEncryptHash` is logged to the terminal for demonstration purposes (`ciphertext` was omitted for brevity):
54+
55+
```bash
56+
ℹ️ dataToEncryptHash: 397acd6bbbf08a2f55da61e8ab552f787f3ad1cc8ccb82dac9fcb6ace26f7148
57+
```
58+
59+
3. Session Signatures were successfully generated for the requested session
60+
4. After the JavaScript test passes, you should see the path for the decrypted file logged to the terminal:
61+
- NOTE: The decrypted file is actually deleted after the test is complete, for cleanup and sanity purposes. You can disable this behavior by commenting out the `afterEach` function in the [./test/index.spec.ts](./test/index.spec.ts) file.
62+
- The test compares the decrypted file to the original file to ensure they are the same before deleting the decrypted file.
63+
64+
```bash
65+
ℹ️ Decrypted content saved to: /Users/user/developer-guides-code/hacker-guides/encryption/encrypt-file/src/loremIpsum-decrypted.txt
66+
```
67+
68+
## Understanding the Code
69+
70+
The following code from [./src/index.ts](./src/index.ts) does the following:
71+
72+
### Reading the Unencrypted File
73+
74+
First, we read the unencrypted file and store its contents in the `fileContent` variable:
75+
76+
```typescript
77+
const filePath = join(process.cwd(), "src", "loremIpsum.txt");
78+
const fileContent = await fs.readFile(filePath, "utf8");
79+
```
80+
81+
When encrypting files with the Lit SDK, it's important to note that the entire file must be loaded into memory as a `Uint8Array` before encryption can begin.
82+
83+
The Lit SDK has a maximum payload size limit of approximately `1.5MB` for encryption operations. Since the SDK currently doesn't support file chunking, attempting to encrypt files larger than this will result in out of memory errors.
84+
85+
For larger files, consider using envelope encryption:
86+
87+
1. Encrypt the large file using a symmetric key (e.g. with AES encryption)
88+
2. Use the Lit SDK to encrypt just the symmetric key itself
89+
90+
### Creating an Ethers signer
91+
92+
As covered in the [Authenticating a Session](../../_getting-started/authenticating-a-session/README.md#creating-an-ethers-signer) guide, the `ethersSigner` is used to generate an Authentication Signature which is a [ERC-5573](https://eips.ethereum.org/EIPS/eip-5573) message that specifies what Lit Resources and corresponding abilities the Session will be authorized to use.
93+
94+
### Defining the Access Control Conditions
95+
96+
The following are the Access Control Conditions that we are using as part of the encryption process. They specify that the data can only be decrypted by the Ethereum address that corresponds to the `ethersSigner` we created earlier:
97+
98+
```typescript
99+
const accessControlConditions: AccessControlConditions = [
100+
{
101+
contractAddress: "",
102+
standardContractType: "",
103+
chain: "ethereum",
104+
method: "",
105+
parameters: [":userAddress"],
106+
returnValueTest: {
107+
comparator: "=",
108+
value: await ethersSigner.getAddress(),
109+
},
110+
},
111+
];
112+
```
113+
114+
These conditions are what you'll want to customize based on your project's use case. You can learn more about the different types of conditions available [here](https://developer.litprotocol.com/category/advanced-topics).
115+
116+
### Encrypting the Data
117+
118+
The following code is what actually encrypts the data using the Lit SDK and the Lit network's public BLS key:
119+
120+
```typescript
121+
const { ciphertext, dataToEncryptHash } = await litNodeClient.encrypt({
122+
dataToEncrypt: new TextEncoder().encode(fileContent),
123+
accessControlConditions,
124+
});
125+
126+
console.log(`ℹ️ dataToEncryptHash: ${dataToEncryptHash}`);
127+
```
128+
129+
`dataToEncrypt` takes a `Uint8Array` as an argument, so we need to convert the file's contents to a `Uint8Array` using `new TextEncoder().encode`.
130+
131+
The output of the `encrypt` method is the `ciphertext` and `dataToEncryptHash`. Coupled with the `accessControlConditions`, this forms the _encryption metadata_ that we'll need to submit to the Lit Network when we want to decrypt the data later.
132+
133+
### Requesting Session Signatures
134+
135+
Our request for session signatures is the same as covered in the [Authenticating a Session](../../_getting-started/authenticating-a-session/README.md#requesting-session-signatures) guide.
136+
137+
However, one thing to note is that the signer of the ERC-5573 message is identity the Lit nodes will use when performing the authorization check for our decryption request. In other words, the Ethereum address derived from the Authentication Signature is what will be checked against the `accessControlConditions` we defined earlier. This derived address **must** match what we specified as `returnValueTest.value` (in the case of this example, that's `await ethersSigner.getAddress()`).
138+
139+
### Decrypting the Data
140+
141+
Lastly, we call the `decrypt` method to decrypt the data. This method takes in the encryption metadata and your Session Signatures as arguments. Similar to when requesting Session Signatures, the `chain` argument signals the signature schema and message format that the Lit nodes will use to authenticate the decryption request (and should almost always be `ethereum`).
142+
143+
The decryption response is returned which is an object with the `Uint8Array` property: `decryptedData`. We can convert this back to a string by calling `new TextDecoder().decode` to get the decrypted data as a string.
144+
145+
```typescript
146+
const decryptionResponse = await litNodeClient.decrypt({
147+
chain: "ethereum",
148+
sessionSigs: sessionSignatures,
149+
ciphertext,
150+
dataToEncryptHash,
151+
accessControlConditions,
152+
});
153+
154+
const decryptedString = new TextDecoder().decode(
155+
decryptionResponse.decryptedData
156+
);
157+
```
158+
159+
Finally we write the decrypted data to a file:
160+
161+
```typescript
162+
const outputPath = join(process.cwd(), "src", "loremIpsum-decrypted.txt");
163+
await fs.writeFile(outputPath, decryptedString, "utf8");
164+
165+
console.log(`ℹ️ Decrypted content saved to: ${outputPath}`);
166+
```
167+
168+
## Next Steps
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "nodejs",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"type": "module",
7+
"scripts": {
8+
"test": "npx @dotenvx/dotenvx run -- mocha test/**/*.spec.ts"
9+
},
10+
"dependencies": {
11+
"@dotenvx/dotenvx": "^0.44.1",
12+
"@lit-protocol/auth-helpers": "^6.11.0",
13+
"@lit-protocol/constants": "^6.11.0",
14+
"@lit-protocol/lit-node-client": "^6.11.0",
15+
"@lit-protocol/types": "^6.11.0",
16+
"ethers": "v5"
17+
},
18+
"devDependencies": {
19+
"@types/chai": "^4.3.16",
20+
"@types/chai-json-schema": "^1.4.10",
21+
"@types/mocha": "^10.0.6",
22+
"chai": "^5.1.1",
23+
"chai-json-schema": "^1.5.1",
24+
"mocha": "^10.4.0",
25+
"tsc": "^2.0.4",
26+
"tsx": "^4.12.0",
27+
"typescript": "^5.4.5"
28+
}
29+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { type LitNodeClient } from "@lit-protocol/lit-node-client";
2+
import {
3+
createSiweMessage,
4+
generateAuthSig,
5+
LitAbility,
6+
LitAccessControlConditionResource,
7+
} from "@lit-protocol/auth-helpers";
8+
import { AccessControlConditions } from "@lit-protocol/types";
9+
import { promises as fs } from "fs";
10+
import { join } from "path";
11+
12+
import { getEnv, getEthersSigner, getLitNodeClient } from "./utils";
13+
14+
const ETHEREUM_PRIVATE_KEY = getEnv("ETHEREUM_PRIVATE_KEY");
15+
16+
export const runExample = async () => {
17+
let litNodeClient: LitNodeClient;
18+
19+
try {
20+
const filePath = join(process.cwd(), "src", "loremIpsum.txt");
21+
const fileContent = await fs.readFile(filePath, "utf8");
22+
23+
const ethersSigner = getEthersSigner(ETHEREUM_PRIVATE_KEY);
24+
litNodeClient = await getLitNodeClient();
25+
26+
const accessControlConditions: AccessControlConditions = [
27+
{
28+
contractAddress: "",
29+
standardContractType: "",
30+
chain: "ethereum",
31+
method: "",
32+
parameters: [":userAddress"],
33+
returnValueTest: {
34+
comparator: "=",
35+
value: await ethersSigner.getAddress(),
36+
},
37+
},
38+
];
39+
40+
const { ciphertext, dataToEncryptHash } = await litNodeClient.encrypt({
41+
dataToEncrypt: new TextEncoder().encode(fileContent),
42+
accessControlConditions,
43+
});
44+
45+
console.log(`ℹ️ dataToEncryptHash: ${dataToEncryptHash}`);
46+
47+
const sessionSignatures = await litNodeClient.getSessionSigs({
48+
chain: "ethereum",
49+
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
50+
resourceAbilityRequests: [
51+
{
52+
resource: new LitAccessControlConditionResource("*"),
53+
ability: LitAbility.AccessControlConditionDecryption,
54+
},
55+
],
56+
authNeededCallback: async ({
57+
uri,
58+
expiration,
59+
resourceAbilityRequests,
60+
}) => {
61+
const toSign = await createSiweMessage({
62+
uri,
63+
expiration,
64+
resources: resourceAbilityRequests,
65+
walletAddress: await ethersSigner.getAddress(),
66+
nonce: await litNodeClient.getLatestBlockhash(),
67+
litNodeClient,
68+
});
69+
70+
return await generateAuthSig({
71+
signer: ethersSigner,
72+
toSign,
73+
});
74+
},
75+
});
76+
77+
const decryptionResponse = await litNodeClient.decrypt({
78+
chain: "ethereum",
79+
sessionSigs: sessionSignatures,
80+
ciphertext,
81+
dataToEncryptHash,
82+
accessControlConditions,
83+
});
84+
85+
const decryptedString = new TextDecoder().decode(
86+
decryptionResponse.decryptedData
87+
);
88+
89+
const outputPath = join(process.cwd(), "src", "loremIpsum-decrypted.txt");
90+
await fs.writeFile(outputPath, decryptedString, "utf8");
91+
92+
console.log(`ℹ️ Decrypted content saved to: ${outputPath}`);
93+
} catch (error) {
94+
console.error(error);
95+
} finally {
96+
litNodeClient!.disconnect();
97+
}
98+
};

0 commit comments

Comments
 (0)