Skip to content
Open
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
11 changes: 5 additions & 6 deletions global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,17 +387,16 @@ declare namespace Lit {

/**
* Encrypt data
* @param {Object} params
* @param {string} params.accessControlConditions The access control conditions
* @param {string} params.to_encrypt The message to encrypt
* @returns {Promise<{ciphertext: string; dataToEncryptHash: string}>} Contains two items: The ciphertext result after encryption, named "ciphertext" and the dataToEncryptHash, named "dataToEncryptHash"
* @param {Object[]} accessControlConditions The access control conditions
* @param {Uint8Array} to_encrypt The message to encrypt
* @returns { {ciphertext: string, dataToEncryptHash: string} } Contains two items: The ciphertext result after encryption, named "ciphertext" and the dataToEncryptHash, named "dataToEncryptHash"
*/
function encrypt({
accessControlConditions,
to_encrypt,
}: {
accessControlConditions: string;
to_encrypt: string;
accessControlConditions: Object[];
to_encrypt: Uint8Array;
}): Promise<{
ciphertext: string;
dataToEncryptHash: string;
Expand Down
85 changes: 85 additions & 0 deletions la-utils/la-account/getEthPrivateKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* Generates an encrypted Ethereum private key that can only be accessed by a specific Ethereum address.
*
* This function:
* 1. Creates a random Ethereum wallet
* 2. Sets up access control conditions based on the provided Ethereum address
* 3. Encrypts the private key using Lit Protocol's encryption
*
* The encryption ensures that only the owner of the specified Ethereum address
* can decrypt and access the private key, providing a secure key management solution.
*
* @param accAddress - The Ethereum address that will have permission to decrypt the private key
* @returns An object containing:
* - publicData: Contains the encrypted data (ciphertext), hash, public key, and access conditions
* - privateData: Contains the unencrypted private key (for initial storage or use)
*
* @example
* const result = await genEncryptedPrivateKey("0x1234...abcd");
* // Store publicData on chain or in public storage
* // Securely handle privateData or discard if not needed
*
{
publicData: {
ciphertext: "sRfc9+j3x/ln4/jmYDihjKKxWfixmns6UaFxuZXDV3ivWZt771VEgsjJSYoKB+s708FIb5alHKIeU7D2fc+zZ/z3pQijhnVg8uWlUrbnGBpHyhjkRXozifZKrajX8jpZBRN31VtTocUFyQvR7TlHHuXI6ojaiKxbYP9Lpuc+cSllTPHmJZhiA+W3atQSVa8ly61wtQH0G10C",
dataToEncryptHash: "1c3d3ffed83ad057d51360e771f56e52e70d86363a159595d1e601a2cb1c5f1d",
keyType: "K256",
publicKey: "0x04068c15f31c625a3ca7c8accc9f7d4290dcd2092e44839828d76d3bd69df5f49dc645ad42beea40653defc6ed5459178428f668f1c0ea832f19cab068d4cb368d",
accs: []
},
privateData: {
privateKey: "0x..."
}
}
*/
export async function genEncryptedPrivateKey(accAddress: string): Promise<{
publicData: {
ciphertext: string;
dataToEncryptHash: string;
keyType: "K256";
publicKey: `0x${string}`;
accs: any[];
};
privateData: {
privateKey: string;
};
}> {
const wallet = ethers.Wallet.createRandom();
const keypair = {
privateKey: wallet.privateKey.toString(),
publicKey: wallet.publicKey,
};

const accs = [
{
contractAddress: "",
standardContractType: "",
chain: "ethereum",
method: "",
parameters: [":userAddress"],
returnValueTest: {
comparator: "=",
value: accAddress,
},
},
];

// -- encrypt the keypair
const { ciphertext, dataToEncryptHash } = await Lit.Actions.encrypt({
accessControlConditions: accs,
to_encrypt: new TextEncoder().encode(`lit_${keypair.privateKey}`),
});

return {
publicData: {
ciphertext,
dataToEncryptHash,
keyType: "K256",
publicKey: keypair.publicKey as `0x${string}`,
accs,
},
privateData: {
privateKey: keypair.privateKey,
},
};
}
199 changes: 199 additions & 0 deletions la-utils/la-db-providers/useorbis/la-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { handleDB } from "./src/handleDB";
import { genEncryptedPrivateKey } from "../../la-account/getEthPrivateKey";
import { hexPrefixed } from "../../la-utils/la-transformer";

/**
* API constants and configuration
*/
const LIT_ACTION_NAME = "db-bro";
const DEFAULT_MESSAGE = "HAKUNA MATATA";

/**
* ========== Types ==========
*/
/**
* Interface for action parameters
*/
interface BaseParams {
action: "register" | "read" | "use";
pkpPublicKey: string;
}

interface UseParams extends BaseParams {
action: "use";
address: string;
customMessage?: string;
}

/**
* Registers a new encrypted private key in the database.
*
* This function:
* 1. Computes the PKP owner address from the public key
* 2. Generates an encrypted private key with access control set to the PKP address
* 3. Stores the metadata in OrbisDB
*
* @param pkpPublicKey - The public key of the PKP that will own the encrypted key
* @returns Object containing the database ID, public data, and owner address
*/
async function register(pkpPublicKey: string) {
pkpPublicKey = hexPrefixed(pkpPublicKey);

// The PKP owns the encrypted private key
const privateKeyOwnerAddress = ethers.utils.computeAddress(pkpPublicKey);

// The PKP address is set as the access control condition
const { publicData, privateData } = await genEncryptedPrivateKey(
privateKeyOwnerAddress
);

// We then write to the OrbisDB table in the columns 'owner' and 'metadata'.
const { id } = await handleDB({
privateKey: privateData.privateKey,
action: "write",
data: {
ownerAddress: privateKeyOwnerAddress,
metadata: JSON.stringify(publicData),
},
});

return {
id,
publicData,
ownerAddress: privateKeyOwnerAddress,
};
}

/**
* Retrieves all stored metadata for the PKP.
*
* @param pkpPublicKey - The public key of the PKP
* @returns Array of metadata objects from the database
*/
async function read(pkpPublicKey: string) {
pkpPublicKey = hexPrefixed(pkpPublicKey);
const privateKeyOwnerAddress = ethers.utils.computeAddress(pkpPublicKey);
const wallet = ethers.Wallet.createRandom();

const res = await handleDB({
privateKey: wallet.privateKey.toString(),
action: "read",
data: {
ownerAddress: privateKeyOwnerAddress,
},
});

const allMetadata = res.rows.map((row) => {
const metadata = JSON.parse(row.metadata);
return {
...metadata,
address: ethers.utils.computeAddress(metadata.publicKey),
};
});

return allMetadata;
}

/**
* Uses a stored private key to sign a message.
*
* This function:
* 1. Retrieves the metadata for the PKP
* 2. Finds the specified address in the metadata
* 3. Decrypts the private key using Lit Protocol
* 4. Signs a message with the decrypted key
*
* @param params - Parameters including pkpPublicKey, address to use, and optional custom message
* @returns Object containing the address, signature, and signed message
*/
async function use(params: UseParams) {
params.pkpPublicKey = hexPrefixed(params.pkpPublicKey);
const allMetadata = await read(params.pkpPublicKey);
const selectedMetadata = allMetadata.find(
(metadata) => metadata.address === params.address
);

if (!selectedMetadata) {
throw new Error(`No metadata found for address ${params.address}`);
}

const decryptRes = await Lit.Actions.decryptAndCombine({
accessControlConditions: selectedMetadata.accs,
ciphertext: selectedMetadata.ciphertext,
dataToEncryptHash: selectedMetadata.dataToEncryptHash,
chain: "ethereum",
authSig: null as unknown as string, // <-- Signed by the PKP on Lit Action, that's why is null.
});

const privateKey = decryptRes.replace("lit_", "");
const wallet = new ethers.Wallet(privateKey);
const signedMessage = params.customMessage || DEFAULT_MESSAGE;
const signature = await wallet.signMessage(signedMessage);

return {
address: wallet.address,
signature,
signedMessage,
};
}

/**
* Main API function that handles all operations (register, read, use) through Lit Actions.
*
* This function determines which action to perform based on the params.action field,
* delegates to the appropriate specialized function, and wraps everything in a Lit.Actions.runOnce call.
*
* @param params - Parameters for the action including action type and PKP public key
* @returns Result of the action
*/
async function runOrbisAction(params: BaseParams | UseParams) {
const res = await Lit.Actions.runOnce(
{
waitForResponse: true,
name: LIT_ACTION_NAME,
},
async () => {
try {
let result;

switch (params.action) {
case "register":
result = await register(params.pkpPublicKey);
break;

case "read":
result = await read(params.pkpPublicKey);
break;

case "use":
result = await use(params as UseParams);
break;

default:
throw new Error(`Unknown action: ${(params as any).action}`);
}

return JSON.stringify({
success: true,
message: result,
});
} catch (error: unknown) {
return JSON.stringify({
success: false,
error:
error instanceof Error ? error.message : "Unknown error occurred",
});
}
}
);

return JSON.parse(res);
}

export const orbisAPI = {
entries: (pubkey: string) =>
runOrbisAction({ action: "read", pkpPublicKey: pubkey }),
register: (pubkey: string) =>
runOrbisAction({ action: "register", pkpPublicKey: pubkey }),
use: (params: UseParams) => runOrbisAction(params),
};
Loading