Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding and Removing Auth Methods for a PKP When it Owns Itself #23

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions pkp-update-authmethod/nodejs/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ETHEREUM_PRIVATE_KEY_A=
ETHEREUM_PRIVATE_KEY_B=
LIT_ACTION_CHECK_ADDRESS_A=QmZNTWzPYHrD7Ce8j24qYyCGxsYc14RY5GsPAXNdiELDug
LIT_ACTION_CHECK_ADDRESS_B=QmZBu2uFuBWdC7jndG2SAz8YqiWn4T2PrdDmRoskLrLYZT
4 changes: 4 additions & 0 deletions pkp-update-authmethod/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"
}
33 changes: 33 additions & 0 deletions pkp-update-authmethod/nodejs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Adding and Removing Auth Methods for a PKP When it Owns Itself

This code example does the following:

1. Mints a new PKP using the ENV `ETHEREUM_PRIVATE_KEY_A`
2. Adds the ENV `LIT_ACTION_CHECK_ADDRESS_A` as an Auth Method for the PKP
3. Verifies `LIT_ACTION_CHECK_ADDRESS_A` was added as an Auth Method
4. Transfers ownership of the PKP from `ETHEREUM_PRIVATE_KEY_A` to the PKP's ETH address
5. Gets PKP Session Signatures using the `LIT_ACTION_CHECK_ADDRESS_A` Lit Action for authorization
6. Transfers some ETH from `ETHEREUM_PRIVATE_KEY_A` to the PKP ETH address
- This is required to pay for the transactions that will add ENV `LIT_ACTION_CHECK_ADDRESS_B` Lit Action as an Auth Method, and remove `LIT_ACTION_CHECK_ADDRESS_A` as an Auth Method for the PKP
- Adding and removing Auth Methods is an onchain actions and requires gas payment
7. Connects a PKP Ethers wallet using the PKP Session Signatures from step 5 to the Lit network
8. Sends a transaction to add `LIT_ACTION_CHECK_ADDRESS_B` as a permitted Auth Method for the PKP
9. Verifies `LIT_ACTION_CHECK_ADDRESS_B` was added as an Auth Method
10. Gets PKP Session Signatures using the `LIT_ACTION_CHECK_ADDRESS_B` Lit Action for authorization
11. Connects a PKP Ethers wallet using the PKP Session Signatures from step 10 to the Lit network
12. Sends a transaction to remove `LIT_ACTION_CHECK_ADDRESS_A` as a permitted Auth Method for the PKP
13. Verifies `LIT_ACTION_CHECK_ADDRESS_A` is no longer a permitted Auth Method for the PKP

### Running this Example

1. `cp .env.example .env`
2. Fill in the ENVs
- `ETHEREUM_PRIVATE_KEY_A` The corresponding address should hold Lit Test tokens on the Chronicle network. It will be used to pay for minting a PKP, adding Auth Methods, transferring PKP ownership, funding the PKP address to pay for adding and removing Auth Methods
- You can obtain test tokens from the faucet [here](https://faucet.litprotocol.com/)
- `ETHEREUM_PRIVATE_KEY_B` The corresponding address is used to generate an authentication signature to test `LIT_ACTION_CHECK_ADDRESS_B` Lit Action Auth Method. Is does not need to have any test tokens, and it will not be used to send transactions
- `LIT_ACTION_CHECK_ADDRESS_A` This is the IPFS CID for [litActionCheckAddressA](./src/litActionCheckAddressA.ts). Update the address on `Line 12` to the corresponding address for `ETHEREUM_PRIVATE_KEY_A`. Then upload the Lit Action to IPFS and copy the CID for this ENV
- `LIT_ACTION_CHECK_ADDRESS_B` This is the IPFS CID for [litActionCheckAddressB](./src/litActionCheckAddressB.ts). Update the address on `Line 12` to the corresponding address for `ETHEREUM_PRIVATE_KEY_B`. Then upload the Lit Action to IPFS and copy the CID for this ENV
3. Install dependencies with: `yarn`
4. Run the code example with: `yarn test`
- The status of the code example will be logged to the console
- If the test is successful, then all of the above happened without error
31 changes: 31 additions & 0 deletions pkp-update-authmethod/nodejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "nodejs",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"test": "npx @dotenvx/dotenvx run -- mocha test/**/*.spec.ts"
},
"devDependencies": {
"@types/chai": "^4.3.16",
"@types/chai-json-schema": "^1.4.10",
"@types/mocha": "^10.0.7",
"chai": "^5.1.1",
"chai-json-schema": "^1.5.1",
"mocha": "^10.5.0",
"tsc": "^2.0.4",
"tsx": "^4.15.7",
"typescript": "^5.5.2"
},
"dependencies": {
"@dotenvx/dotenvx": "^1.0.0",
"@lit-protocol/auth-helpers": "^6.1.1",
"@lit-protocol/constants": "^6.1.1",
"@lit-protocol/contracts-sdk": "^6.1.1",
"@lit-protocol/lit-auth-client": "^6.1.1",
"@lit-protocol/lit-node-client": "^6.1.1",
"@lit-protocol/pkp-ethers": "^6.1.1",
"bs58": "^6.0.0",
"ethers": "v5"
}
}
299 changes: 299 additions & 0 deletions pkp-update-authmethod/nodejs/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import * as ethers from "ethers";
import { AuthMethodScope, LitNetwork } from "@lit-protocol/constants";
import { LitContracts } from "@lit-protocol/contracts-sdk";
import {
LitAbility,
LitActionResource,
LitPKPResource,
createSiweMessageWithRecaps,
generateAuthSig,
} from "@lit-protocol/auth-helpers";
import { PKPEthersWallet } from "@lit-protocol/pkp-ethers";
import bs58 from "bs58";

import { getEnv } from "./utils";

const ETHEREUM_PRIVATE_KEY_A = getEnv("ETHEREUM_PRIVATE_KEY_A");
const ETHEREUM_PRIVATE_KEY_B = getEnv("ETHEREUM_PRIVATE_KEY_B");
const LIT_ACTION_CHECK_ADDRESS_A = getEnv("LIT_ACTION_CHECK_ADDRESS_A");
const LIT_ACTION_CHECK_ADDRESS_B = getEnv("LIT_ACTION_CHECK_ADDRESS_B");
const LIT_ACTION_A_IPFS_CID_BYTES = `0x${Buffer.from(
bs58.decode(LIT_ACTION_CHECK_ADDRESS_A)
).toString("hex")}`;
const LIT_ACTION_B_IPFS_CID_BYTES = `0x${Buffer.from(
bs58.decode(LIT_ACTION_CHECK_ADDRESS_B)
).toString("hex")}`;

export const runTheExample = async () => {
let litNodeClient: LitNodeClient;

try {
const ethersSignerA = new ethers.Wallet(
ETHEREUM_PRIVATE_KEY_A,
new ethers.providers.JsonRpcProvider(
"https://vesuvius-rpc.litprotocol.com/"
)
);
const ethersSignerB = new ethers.Wallet(
ETHEREUM_PRIVATE_KEY_B,
new ethers.providers.JsonRpcProvider(
"https://vesuvius-rpc.litprotocol.com/"
)
);

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

console.log("🔄 Minting new PKP...");
const mintedPkp = (await litContracts.pkpNftContractUtils.write.mint()).pkp;
console.log(
`✅ Minted new PKP with public key: ${mintedPkp.publicKey} and ETH address: ${mintedPkp.ethAddress}`
);

console.log("🔄 Adding Lit Action Auth Method A to PKP...");
const addAuthMethodAReceipt = await litContracts.addPermittedAction({
pkpTokenId: mintedPkp.tokenId,
ipfsId: LIT_ACTION_CHECK_ADDRESS_A,
authMethodScopes: [AuthMethodScope.SignAnything],
});
console.log(
`✅ Added Lit Action Auth Method A to PKP. Transaction hash: ${addAuthMethodAReceipt.transactionHash}`
);

console.log("🔄 Checking Lit Action Auth Method A permitted for PKP...");
let isPermittedA =
await litContracts.pkpPermissionsContract.read.isPermittedAction(
mintedPkp.tokenId,
LIT_ACTION_A_IPFS_CID_BYTES
);
if (!isPermittedA)
throw new Error("Lit Action Auth Method A is not permitted for the PKP");
console.log("✅ Lit Action Auth Method A is permitted for PKP");

console.log("🔄 Transferring ownership of PKP to itself...");
const transferPkpOwnershipReceipt = await (
await litContracts.pkpNftContract.write.transferFrom(
ethersSignerA.address,
mintedPkp.ethAddress,
mintedPkp.tokenId,
{
gasLimit: 125_000,
}
)
).wait();
console.log(
`✅ Transferred ownership of PKP NFT. Transaction hash: ${transferPkpOwnershipReceipt.transactionHash}`
);

console.log("🔄 Connecting LitNodeClient to Lit network...");
litNodeClient = new LitNodeClient({
litNetwork: LitNetwork.DatilDev,
debug: false,
rpcUrl: "https://vesuvius-rpc.litprotocol.com/",
});
await litNodeClient.connect();
console.log("✅ Connected LitNodeClient to Lit network");

console.log(
"🔄 Getting PKP Session Sigs using Lit Action Auth Method A..."
);
const pkpSessionSigsA = await litNodeClient.getLitActionSessionSigs({
pkpPublicKey: mintedPkp.publicKey,
resourceAbilityRequests: [
{
resource: new LitPKPResource("*"),
ability: LitAbility.PKPSigning,
},
{
resource: new LitActionResource("*"),
ability: LitAbility.LitActionExecution,
},
],
litActionIpfsId: LIT_ACTION_CHECK_ADDRESS_A,
jsParams: {
authSig: JSON.stringify(
await generateAuthSig({
signer: ethersSignerA,
// @ts-ignore
toSign: await createSiweMessageWithRecaps({
uri: "http://localhost",
expiration: new Date(
Date.now() + 1000 * 60 * 60 * 24
).toISOString(), // 24 hours
walletAddress: ethersSignerA.address,
nonce: await litNodeClient.getLatestBlockhash(),
litNodeClient,
}),
})
),
},
});
console.log("✅ Got PKP Session Sigs using Lit Action Auth Method A");

console.log(
"🔄 Funding PKP ETH Address to be able to add and remove Lit Actions as permitted Auth Methods for PKP..."
);
const fundPkpTxReceipt = await (
await ethersSignerA.sendTransaction({
to: mintedPkp.ethAddress,
value: ethers.utils.parseEther("0.001"),
})
).wait();
console.log(
`✅ Funded PKP ETH Address. Transaction hash: ${fundPkpTxReceipt.transactionHash}`
);

console.log(
`🔄 Checking Lit token balance for PKP ${mintedPkp.ethAddress}...`
);
const balance = await ethersSignerA.provider.getBalance(
mintedPkp.ethAddress,
"latest"
);
console.log(`✅ Got balance: ${ethers.utils.formatEther(balance)} ether`);

const pkpEthersWalletA = new PKPEthersWallet({
litNodeClient,
pkpPubKey: mintedPkp.publicKey,
controllerSessionSigs: pkpSessionSigsA,
});
await pkpEthersWalletA.init();

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

console.log("🔄 Adding Lit Action Auth Method B to PKP...");
const addAuthMethodBReceipt = await (
await litContractsPkpSignerA.pkpPermissionsContract.write.addPermittedAction(
mintedPkp.tokenId,
LIT_ACTION_B_IPFS_CID_BYTES,
[AuthMethodScope.SignAnything],
{
gasPrice: "1",
gasLimit: 250_000,
}
)
).wait();
console.log(
`✅ Added Lit Action Auth Method B to PKP. Transaction hash: ${addAuthMethodBReceipt.transactionHash}`
);

console.log("🔄 Checking Lit Action Auth Method B permitted for PKP...");
const isPermittedB =
await litContracts.pkpPermissionsContract.read.isPermittedAction(
mintedPkp.tokenId,
LIT_ACTION_B_IPFS_CID_BYTES
);
if (!isPermittedB)
throw new Error("Lit Action Auth Method B is not permitted for the PKP");
console.log("✅ Lit Action Auth Method B is permitted for PKP");

console.log(
"🔄 Getting PKP Session Sigs using Lit Action Auth Method B..."
);
const pkpSessionSigsB = await litNodeClient.getLitActionSessionSigs({
pkpPublicKey: mintedPkp.publicKey,
resourceAbilityRequests: [
{
resource: new LitPKPResource("*"),
ability: LitAbility.PKPSigning,
},
{
resource: new LitActionResource("*"),
ability: LitAbility.LitActionExecution,
},
],
litActionIpfsId: LIT_ACTION_CHECK_ADDRESS_B,
jsParams: {
authSig: JSON.stringify(
await generateAuthSig({
signer: ethersSignerB,
// @ts-ignore
toSign: await createSiweMessageWithRecaps({
uri: "http://localhost",
expiration: new Date(
Date.now() + 1000 * 60 * 60 * 24
).toISOString(), // 24 hours
walletAddress: ethersSignerB.address,
nonce: await litNodeClient.getLatestBlockhash(),
litNodeClient,
}),
})
),
},
});
console.log("✅ Got PKP Session Sigs using Lit Action Auth Method B");

const pkpEthersWalletB = new PKPEthersWallet({
litNodeClient,
pkpPubKey: mintedPkp.publicKey,
controllerSessionSigs: pkpSessionSigsB,
});
await pkpEthersWalletB.init();

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

console.log("🔄 Removing Lit Action Auth Method A from PKP...");
const removeAuthMethodAReceipt = await (
await litContractsPkpSignerB.pkpPermissionsContract.write.removePermittedAction(
mintedPkp.tokenId,
LIT_ACTION_A_IPFS_CID_BYTES,
{
gasPrice: await ethersSignerA.provider.getGasPrice(),
gasLimit: 100_000,
}
)
).wait();
console.log(
`✅ Removed Lit Action Auth Method B from PKP. Transaction hash: ${removeAuthMethodAReceipt.transactionHash}`
);

console.log(
"🔄 Checking Lit Action Auth Method A is no longer permitted for PKP..."
);
isPermittedA =
await litContracts.pkpPermissionsContract.read.isPermittedAction(
mintedPkp.tokenId,
LIT_ACTION_A_IPFS_CID_BYTES
);
if (isPermittedA)
throw new Error(
"Lit Action Auth Method A is still permitted for the PKP when it's supposed to have been removed"
);
console.log("✅ Lit Action Auth Method A is no longer permitted for PKP");

return true;
} catch (error) {
console.error(error);
} finally {
litNodeClient!.disconnect();
}
};
Loading