Skip to content

Commit 92484a0

Browse files
committed
Init encrypt string example
1 parent efe2e76 commit 92484a0

File tree

9 files changed

+4785
-0
lines changed

9 files changed

+4785
-0
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ETHEREUM_PRIVATE_KEY=
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: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Encrypting a String
2+
3+
After connecting to the Lit Network and authenticating a session, you can encrypt a string 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 string, as an array of bytes, 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.
4+
5+
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.
6+
7+
When you want to decrypt the string, 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 string.
8+
9+
- [Encrypting a String](#encrypting-a-string)
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+
- [Creating an Ethers signer](#creating-an-ethers-signer)
17+
- [Defining the Access Control Conditions](#defining-the-access-control-conditions)
18+
- [Encrypting the Data](#encrypting-the-data)
19+
- [Requesting Session Signatures](#requesting-session-signatures)
20+
- [Decrypting the Data](#decrypting-the-data)
21+
- [Next Steps](#next-steps)
22+
23+
## Prerequisites
24+
25+
- Understanding of Lit core terminology and concepts covered [here](../README.md#core-terminology)
26+
- Understanding of Lit encryption terminology and concepts covered [here](../README.md#relevant-terminology)
27+
- Understanding of the [Connecting to the Lit Network](../connecting-to-lit/README.md) guide
28+
- Understanding of the [Authenticating a Session](../../_getting-started/authenticating-a-session/README.md) guide
29+
30+
## Running the Code Example
31+
32+
### Requirements
33+
34+
- [Node.js](https://nodejs.org/en)
35+
- [Yarn](https://yarnpkg.com/getting-started)
36+
- `@lit-protocol/constants`
37+
- `@lit-protocol/lit-node-client`
38+
- `@lit-protocol/auth-helpers`
39+
- `@lit-protocol/types`
40+
41+
### Steps
42+
43+
1. `yarn` to install the dependencies
44+
2. `yarn test` to run the code example
45+
46+
### Expected Output
47+
48+
After running the code example, you should see output in your terminal:
49+
50+
1. An indication that a connection to the Lit Network was successfully established
51+
2. The string was successfully encrypted
52+
- The `ciphertext` and `dataToEncryptHash` are logged to the terminal for demonstration purposes:
53+
54+
```bash
55+
ℹ️ ciphertext: mRLJXD7wAMsvBoq9Gb60bn+ofuZ0srzdU/cEkSvagDvwCr3X5l1tUNuHUXgTrLGbiv/iT7o7qtJ73pFQwRmjpmH4bQ2klUGEvdgTUElrHtQ4SSfSrqYY5tciNjmIip7ifS7oNnHMArK2vuld6ZVqW8VnonSNyVdefaxckYLAovxdVrssUi7U2okC
56+
ℹ️ dataToEncryptHashh: 5ad8f16e45a2f21c693ea4e9376e46424abbf8f74838a5bd8f6173c54ba2e87a
57+
```
58+
59+
3. Session Signatures were successfully generated for the requested session
60+
4. After the JavaScript test passes, you should see the decrypted string logged to the terminal:
61+
62+
```bash
63+
ℹ️ decryptedString: The answer to life, the universe, and everything is 42.
64+
```
65+
66+
## Understanding the Code
67+
68+
The following code from [./src/index.ts](./src/index.ts) does the following:
69+
70+
### Creating an Ethers signer
71+
72+
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.
73+
74+
### Defining the Access Control Conditions
75+
76+
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:
77+
78+
```typescript
79+
const accessControlConditions: AccessControlConditions = [
80+
{
81+
contractAddress: "",
82+
standardContractType: "",
83+
chain: "ethereum",
84+
method: "",
85+
parameters: [":userAddress"],
86+
returnValueTest: {
87+
comparator: "=",
88+
value: await ethersSigner.getAddress(),
89+
},
90+
},
91+
];
92+
```
93+
94+
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).
95+
96+
### Encrypting the Data
97+
98+
The following code is what actually encrypts the data using the Lit SDK and the Lit network's public BLS key:
99+
100+
```typescript
101+
const { ciphertext, dataToEncryptHash } = await litNodeClient.encrypt({
102+
dataToEncrypt: new TextEncoder().encode(
103+
"The answer to life, the universe, and everything is 42."
104+
),
105+
accessControlConditions,
106+
});
107+
108+
console.log(`ℹ️ ciphertext: ${ciphertext}`);
109+
console.log(`ℹ️ dataToEncryptHashh: ${dataToEncryptHash}`);
110+
```
111+
112+
`dataToEncrypt` takes a `Uint8Array` as an argument, so we need to convert the string to a `Uint8Array` using `new TextEncoder().encode`.
113+
114+
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.
115+
116+
### Requesting Session Signatures
117+
118+
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.
119+
120+
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()`).
121+
122+
### Decrypting the Data
123+
124+
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`).
125+
126+
Finally 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 our decrypted string.
127+
128+
```typescript
129+
const decryptionResponse = await litNodeClient.decrypt({
130+
chain: "ethereum",
131+
sessionSigs: sessionSignatures,
132+
ciphertext,
133+
dataToEncryptHash,
134+
accessControlConditions,
135+
});
136+
137+
const decryptedString = new TextDecoder().decode(
138+
decryptionResponse.decryptedData
139+
);
140+
console.log(`ℹ️ decryptedString: ${decryptedString}`);
141+
```
142+
143+
## 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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
10+
import { getEnv, getEthersSigner, getLitNodeClient } from "./utils";
11+
12+
const ETHEREUM_PRIVATE_KEY = getEnv("ETHEREUM_PRIVATE_KEY");
13+
14+
export const runExample = async () => {
15+
let litNodeClient: LitNodeClient;
16+
17+
try {
18+
const ethersSigner = getEthersSigner(ETHEREUM_PRIVATE_KEY);
19+
litNodeClient = await getLitNodeClient();
20+
21+
const accessControlConditions: AccessControlConditions = [
22+
{
23+
contractAddress: "",
24+
standardContractType: "",
25+
chain: "ethereum",
26+
method: "",
27+
parameters: [":userAddress"],
28+
returnValueTest: {
29+
comparator: "=",
30+
value: await ethersSigner.getAddress(),
31+
},
32+
},
33+
];
34+
35+
const { ciphertext, dataToEncryptHash } = await litNodeClient.encrypt({
36+
dataToEncrypt: new TextEncoder().encode(
37+
"The answer to life, the universe, and everything is 42."
38+
),
39+
accessControlConditions,
40+
});
41+
42+
console.log(`ℹ️ ciphertext: ${ciphertext}`);
43+
console.log(`ℹ️ dataToEncryptHashh: ${dataToEncryptHash}`);
44+
45+
const sessionSignatures = await litNodeClient.getSessionSigs({
46+
chain: "ethereum",
47+
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
48+
resourceAbilityRequests: [
49+
{
50+
resource: new LitAccessControlConditionResource("*"),
51+
ability: LitAbility.AccessControlConditionDecryption,
52+
},
53+
],
54+
authNeededCallback: async ({
55+
uri,
56+
expiration,
57+
resourceAbilityRequests,
58+
}) => {
59+
const toSign = await createSiweMessage({
60+
uri,
61+
expiration,
62+
resources: resourceAbilityRequests,
63+
walletAddress: await ethersSigner.getAddress(),
64+
nonce: await litNodeClient.getLatestBlockhash(),
65+
litNodeClient,
66+
});
67+
68+
return await generateAuthSig({
69+
signer: ethersSigner,
70+
toSign,
71+
});
72+
},
73+
});
74+
75+
const decryptionResponse = await litNodeClient.decrypt({
76+
chain: "ethereum",
77+
sessionSigs: sessionSignatures,
78+
ciphertext,
79+
dataToEncryptHash,
80+
accessControlConditions,
81+
});
82+
83+
const decryptedString = new TextDecoder().decode(
84+
decryptionResponse.decryptedData
85+
);
86+
console.log(`ℹ️ decryptedString: ${decryptedString}`);
87+
return decryptedString;
88+
} catch (error) {
89+
console.error(error);
90+
} finally {
91+
litNodeClient!.disconnect();
92+
}
93+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { LitNodeClient } from "@lit-protocol/lit-node-client";
2+
import { LIT_RPC, LitNetwork } from "@lit-protocol/constants";
3+
import { ethers } from "ethers";
4+
5+
export const getEnv = (name: string): string => {
6+
const env = process.env[name];
7+
if (env === undefined || env === "")
8+
throw new Error(
9+
`${name} ENV is not defined, please define it in the .env file`
10+
);
11+
return env;
12+
};
13+
14+
export const getEthersSigner = (ethereumPrivateKey: string) => {
15+
return new ethers.Wallet(
16+
ethereumPrivateKey,
17+
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
18+
);
19+
};
20+
21+
export const getLitNodeClient = async () => {
22+
const litNodeClient = new LitNodeClient({
23+
litNetwork: LitNetwork.DatilDev,
24+
debug: true,
25+
});
26+
await litNodeClient.connect();
27+
return litNodeClient;
28+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { expect } from "chai";
2+
3+
import { runExample } from "../src";
4+
5+
describe("Encrypting and decrypting a string", () => {
6+
it("should encrypt and decrypt a string", async () => {
7+
const decryptedString = await runExample();
8+
expect(decryptedString).to.equal(
9+
"The answer to life, the universe, and everything is 42."
10+
);
11+
}).timeout(120_000);
12+
});

0 commit comments

Comments
 (0)