Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions decrypt-api-key-in-action/.gitignore

This file was deleted.

52 changes: 0 additions & 52 deletions decrypt-api-key-in-action/README.md

This file was deleted.

3 changes: 3 additions & 0 deletions decrypt-api-key-in-action/nodejs/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ETHEREUM_PRIVATE_KEY=
ALCHEMY_API_KEY=
LIT_CAPACITY_CREDIT_TOKEN_ID=
4 changes: 4 additions & 0 deletions decrypt-api-key-in-action/nodejs/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"$schema": "https://json.schemastore.org/mocharc.json",
"require": "tsx"
}
48 changes: 48 additions & 0 deletions decrypt-api-key-in-action/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# `decryptAndCombine` Lit Action Code Example

This code demonstrates how to use the Lit and Lit Actions SDKs to implement the `decryptAndCombine` method in a Lit Action. This method collects and combines the decryption shares from each of the Lit nodes onto a single Lit node, where the given content is decrypted. In this example, that means the API key remains in [Lit's Trusted Execution Environment (TEE)](https://developer.litprotocol.com/resources/how-it-works#sealed-and-confidential-hardware) and is not exposed to the client.

## Understanding the Implementation

1. Using an imported Ethereum private key, connect the wallet to the Lit RPC endpoint `Chronicle Yellowstone`
2. Connect to the Lit network using the `LitNodeClient` on the `datil-test` network
3. Connect the `LitContracts` client to the Lit network (`datil-test` in this case)
4. **If not provided in the .env file**: Mint a [`capacityCreditsNFT`](https://developer.litprotocol.com/sdk/capacity-credits) and define the request limit and expiration date
5. Define the Access Control Conditions (ACCs) required to decrypt the data, and encrypt the API key using the `encryptString` function
6. Generate an ACCs resource string. This will be used when we generate session signatures to specify that our current session is permitted to only decrypt the encrypted data we have created
7. Use the `executeJs` method to execute the Lit Action code. This step also involves generating the session signatures, specifying decryption and Lit Action execution as abilities for our session

## **NOTE**

Running the test in this repository will make an HTTP request to the Base Mainnet, querying the current blocknumber. If you'd like to use a different blockchain, change the URL in the Lit Action file and run the test again.

---

## Running the Examples

### Install the Dependencies

In this directory, `decrypt-api-key-in-action/nodejs`, run `yarn` to install the project dependencies.

### Setting up the `.env` File

Make a copy of the provided `.env.example` file and name it `.env`:

```
cp .env.example .env
```

Within the `.env` file there are the following ENVs:

1. `ETHEREUM_PRIVATE_KEY` - **Required**
- Must have Lit `tstLPX` tokens on the `Chronicle Yellowstone` blockchain
- [Faucet for Chronicle Yellowstone](https://chronicle-yellowstone-faucet.getlit.dev/)
- Will be used to pay for Lit usage
2. `ALCHEMY_API_KEY` - **Required**
- The Alchemy API key that will be encrypted and later decrypted in the Lit Action. Afterwards, it will be used to make an HTTP request to the Base Mainnet blockchain. If you need an Alchemy API key, you can make an account on [their website](https://www.alchemy.com/)
3. `LIT_CAPACITY_CREDIT_TOKEN_ID` - **Optional**
- If not provided, a new `capacityCreditsNFT` will be minted and used. This enables the `ETHEREUM_PRIVATE_KEY` to pay for Lit usage

### Running the Test

After the `.env` is configured, there is a NPM script in the `package.json` to run the test in the `test/decryptApiKeyInActionTest.spec.ts` file. To run the test, use the `yarn test` command.
28 changes: 28 additions & 0 deletions decrypt-api-key-in-action/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "decryption-in-lit-action",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"test": "npx @dotenvx/dotenvx run -- mocha test/**/*.spec.ts"
},
"devDependencies": {
"@types/chai": "^4.3.19",
"@types/chai-json-schema": "^1.4.10",
"@types/mocha": "^10.0.8",
"chai": "^5.1.1",
"chai-json-schema": "^1.5.1",
"mocha": "^10.7.3",
"ts-node": "^10.9.2",
"tsc": "^2.0.4",
"tsx": "^4.19.1",
"typescript": "^5.6.2"
},
"dependencies": {
"@dotenvx/dotenvx": "^0.44.1",
"@lit-protocol/auth-helpers": "^6.4.10",
"@lit-protocol/constants": "^6.4.10",
"@lit-protocol/contracts-sdk": "^6.4.10",
"@lit-protocol/lit-node-client": "^6.4.10"
}
}
167 changes: 167 additions & 0 deletions decrypt-api-key-in-action/nodejs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { LitNodeClient, encryptString } from "@lit-protocol/lit-node-client";
import { LitNetwork, LIT_RPC } from "@lit-protocol/constants";
import {
createSiweMessage,
LitAbility,
LitAccessControlConditionResource,
LitActionResource,
generateAuthSig,
} from "@lit-protocol/auth-helpers";
import { LitContracts } from "@lit-protocol/contracts-sdk";
import { AccessControlConditions } from "@lit-protocol/types";
import * as ethers from "ethers";

import { litActionCode } from "./litAction";
import { getEnv } from "./utils";

const LIT_NETWORK = LitNetwork.DatilTest;
const ETHEREUM_PRIVATE_KEY = getEnv("ETHEREUM_PRIVATE_KEY");
const LIT_CAPACITY_CREDIT_TOKEN_ID =
process.env["LIT_CAPACITY_CREDIT_TOKEN_ID"];

export const decryptApiKey = async (alchemyUrl: string, key: string) => {
let litNodeClient: LitNodeClient;

try {
const ethersWallet = new ethers.Wallet(
ETHEREUM_PRIVATE_KEY,
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
);

console.log("🔄 Connecting to the Lit network...");
litNodeClient = new LitNodeClient({
litNetwork: LIT_NETWORK,
debug: false,
});
await litNodeClient.connect();
console.log("✅ Connected to the Lit network");

console.log("🔄 Connecting LitContracts client to network...");
const litContracts = new LitContracts({
signer: ethersWallet,
network: LIT_NETWORK,
debug: false,
});
await litContracts.connect();
console.log("✅ Connected LitContracts client to network");

let capacityTokenId = LIT_CAPACITY_CREDIT_TOKEN_ID;
if (capacityTokenId === "" || capacityTokenId === undefined) {
console.log("🔄 No Capacity Credit provided, minting a new one...");
capacityTokenId = (
await litContracts.mintCapacityCreditsNFT({
requestsPerKilosecond: 10,
daysUntilUTCMidnightExpiration: 1,
})
).capacityTokenIdStr;
console.log(`✅ Minted new Capacity Credit with ID: ${capacityTokenId}`);
} else {
console.log(
`ℹ️ Using provided Capacity Credit with ID: ${LIT_CAPACITY_CREDIT_TOKEN_ID}`
);
}

console.log("🔄 Creating capacityDelegationAuthSig...");
const { capacityDelegationAuthSig } =
await litNodeClient.createCapacityDelegationAuthSig({
dAppOwnerWallet: ethersWallet,
capacityTokenId,
delegateeAddresses: [ethersWallet.address],
uses: "1",
});
console.log("✅ Capacity Delegation Auth Sig created");

const accessControlConditions: AccessControlConditions = [
{
contractAddress: "",
standardContractType: "",
chain: "ethereum",
method: "eth_getBalance",
parameters: [":userAddress", "latest"],
returnValueTest: {
comparator: ">=",
value: "0",
},
},
];

console.log("🔐 Encrypting the API key...");
const { ciphertext, dataToEncryptHash } = await encryptString(
{
accessControlConditions,
dataToEncrypt: key,
},
litNodeClient
);
console.log("✅ Encrypted the API key");
console.log("ℹ️ The base64-encoded ciphertext:", ciphertext);
console.log(
"ℹ️ The hash of the data that was encrypted:",
dataToEncryptHash
);

console.log("🔄 Generating the Resource String...");
const accsResourceString =
await LitAccessControlConditionResource.generateResourceString(
accessControlConditions as any,
dataToEncryptHash
);
console.log("✅ Generated the Resource String");

console.log("🔄 Getting the Session Signatures...");
const sessionSigs = await litNodeClient.getSessionSigs({
chain: "ethereum",
capabilityAuthSigs: [capacityDelegationAuthSig],
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
resourceAbilityRequests: [
{
resource: new LitAccessControlConditionResource(accsResourceString),
ability: LitAbility.AccessControlConditionDecryption,
},
{
resource: new LitActionResource("*"),
ability: LitAbility.LitActionExecution,
},
],
authNeededCallback: async ({
uri,
expiration,
resourceAbilityRequests,
}) => {
const toSign = await createSiweMessage({
uri,
expiration,
resources: resourceAbilityRequests,
walletAddress: ethersWallet.address,
nonce: await litNodeClient.getLatestBlockhash(),
litNodeClient,
});

return await generateAuthSig({
signer: ethersWallet,
toSign,
});
},
});
console.log("✅ Generated the Session Signatures");

console.log("🔄 Executing the Lit Action...");
const litActionSignatures = await litNodeClient.executeJs({
sessionSigs,
code: litActionCode,
jsParams: {
accessControlConditions,
ciphertext,
dataToEncryptHash,
alchemyUrl,
},
});
console.log("✅ Executed the Lit Action");

return litActionSignatures;
} catch (error) {
console.error(error);
} finally {
litNodeClient!.disconnect();
}
};
46 changes: 46 additions & 0 deletions decrypt-api-key-in-action/nodejs/src/litAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// @ts-nocheck

const _litActionCode = async () => {
try {
const apiKey = await Lit.Actions.decryptAndCombine({
accessControlConditions,
ciphertext,
dataToEncryptHash,
authSig: null,
chain: "ethereum",
});

const blockNumber = await Lit.Actions.runOnce(
{ waitForResponse: true, name: "ETH block number" },
async () => {
const resp = await fetch(`${alchemyUrl}${apiKey}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "eth_blockNumber",
params: [],
}),
});

let data = await resp.json();

if (data.result) {
data.result = parseInt(data.result, 16);
return data.result;
} else {
throw new Error("Failed to get block number");
}
}
);

Lit.Actions.setResponse({ response: blockNumber });
} catch (e) {
Lit.Actions.setResponse({ response: e.message });
}
};

export const litActionCode = `(${_litActionCode.toString()})();`;
8 changes: 8 additions & 0 deletions decrypt-api-key-in-action/nodejs/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const getEnv = (name: string): string => {
const env = process.env[name];
if (env === undefined || env === "")
throw new Error(
`${name} ENV is not defined, please define it in the .env file`
);
return env;
};
Loading