Skip to main content

Making Your First Signing Request

This guide will walk you through an example of signing data using a Programmable Key Pair (PKP).

PKPs are programmable key pairs that can be used to sign data. They are typically used with Lit Actions while executing code on the Lit network, but they can also be used standalone.

We will cover:

  • Connecting to the Lit network
  • Minting a PKP owned by an Ethereum wallet
  • Signing a message with the PKP

This guide uses Lit's Datil-dev Network, a free test network designed for developers to familiarize themselves with the Lit SDK. Since no payment is required, the code is less complex. For building production-ready applications, the Datil-test Network is recommended. Once your application is ready for deployment, you can move it to Datil, the Lit production network.

info

For more in-depth guides on PKP signing, please see the PKPs section.

Installing the Example Dependencies

To start PKP signing with theLit SDK, you'll need to install these packages:

  • @lit-protocol/lit-node-client: The core Lit SDK package.
  • @lit-protocol/constants: A package containing useful constants across the SDK.
  • @lit-protocol/auth-helpers: A package containing useful functions for generating Session Signatures and authentication.
  • @lit-protocol/contracts-sdk: A package containing useful functions for interacting with the Chronicle Yellowstone blockchain.
  • ethers@v5: A package for interacting with Ethereum, required for wallet operations.
npm install @lit-protocol/lit-node-client \
@lit-protocol/constants \
@lit-protocol/auth-helpers \
@lit-protocol/contracts-sdk \
ethers@v5

If you're just getting started with Lit or development in general, we recommend taking a look at our Starter Guides. These guides provide an environment for getting started with the Lit SDK.

Walkthrough

Connecting to the Lit Network

As covered in the Connecting to Lit guide, signing with Lit requires an active connection to the Lit network. This is achieved by initializing a LitNodeClient instance, which connects to Lit nodes.

Additionally, we'll initialize an Ethereum wallet using the ETHEREUM_PRIVATE_KEY environment variable. This wallet is essential for:

  • Generating Session Signatures, which authenticate your requests with the Lit network.
  • Minting the PKP, as it allows us to pay the minting fee on the blockchain.
Click here to see how this is done

import { LitNodeClient } from "@lit-protocol/lit-node-client";
import { LIT_NETWORK, LIT_RPC } from "@lit-protocol/constants";
import * as ethers from "ethers";

const litNodeClient = new LitNodeClient({
litNetwork: LIT_NETWORK.DatilDev,
debug: false
});
await litNodeClient.connect();

const ethersWallet = new ethers.Wallet(
process.env.ETHEREUM_PRIVATE_KEY!, // Replace with your private key
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
);

Minting a PKP

In order to sign data with a PKP, we must first mint one. To mint the PKP, we'll use the LitContracts class, which interacts with the Chronicle Yellowstone blockchain—Lit's custom EVM rollup. By setting the signer to the wallet we initialized earlier, we can pay the PKP minting fee using the Lit test token.

Click here to see how this is done

import { LitContracts } from "@lit-protocol/contracts-sdk";

const litContracts = new LitContracts({
signer: ethersWallet,
network: LIT_NETWORK.DatilDev,
debug: false
});
await litContracts.connect();

const pkpInfo = (await litContracts.pkpNftContractUtils.write.mint()).pkp;

Generating Session Signatures

As covered in the Generating Session Signatures guide, Session Signatures authenticate your interactions with the Lit network and are required to sign data using a PKP.

In this step, we'll generate Session Signatures that grant permission to:

  • Sign with a Specific PKP: We specify that the session can only use the PKP we just minted by including its token ID in the resource ability requests.
  • Execute Lit Actions: We enable the Lit Action Execution resource because the pkpSign method requires permission to execute Lit Actions.

These permissions ensure that the session can only perform specific actions with defined resources, enhancing security.

Click here to see how this is done

import { LIT_ABILITY } from "@lit-protocol/constants";
import {
createSiweMessage,
generateAuthSig,
LitActionResource,
LitPKPResource,
} from "@lit-protocol/auth-helpers";

const sessionSigs = await litNodeClient.getSessionSigs({
chain: "ethereum",
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
resourceAbilityRequests: [
{
resource: new LitPKPResource(pkpInfo.tokenId),
ability: LIT_ABILITY.PKPSigning,
},
{
resource: new LitActionResource("*"),
ability: LIT_ABILITY.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,
});
},
});

Signing Data

With the generated Session Signatures, we can use the pkpSign method to sign our data using the PKP. In this example, we're signing the hash of the message "The answer to the universe is 42.".

If you'd like to see the pkpSign method's parameters, you can find them here.

Click here to see how this is done

const signingResult = await litNodeClient.pkpSign({
pubKey: pkpInfo.publicKey,
sessionSigs,
toSign: ethers.utils.arrayify(
ethers.utils.keccak256(
ethers.utils.toUtf8Bytes("The answer to the universe is 42.")
)
),
})

Our signingResult will appear as an ECDSA signature:

Click here to see the signature

{
r: '2755ed0cc55452c5c1ba75cad13167c537a44a6cd0fdb9da3e48a05bf8de3c5d',
s: '3458584d1524f9d52aef1ec97386f1914fcf948f2b63c8fd8406dec38be0744f',
recid: 0,
signature: '0x2755ed0cc55452c5c1ba75cad13167c537a44a6cd0fdb9da3e48a05bf8de3c5d3458584d1524f9d52aef1ec97386f1914fcf948f2b63c8fd8406dec38be0744f1b',
publicKey: '045931A629B8C00995A86E3CE6880416EE082240BE6E7FD144648115E6FB9ECB525D4B6F6CADCB17D39F318828A66E71DA501C529478C090CD682876C2F4258D49',
dataSigned: '760404FCE401CD30392E61B48DED0382A9987C18793093A52BA25E443B20F58A'
}

We can easily verify that the returned signature was made by our PKP by recovering the public key and Ethereum address from signingResult:

Click here to see how this is done

const encodedSig = ethers.utils.joinSignature({
v: signingResult.recid,
r: `0x${signingResult.r}`,
s: `0x${signingResult.s}`,
});

const recoveredPubkey = ethers.utils.recoverPublicKey(
`0x${signingResult.dataSigned}`,
encodedSig
);
const recoveredAddress = ethers.utils.recoverAddress(
`0x${signingResult.dataSigned}`,
encodedSig
);

console.log(recoveredPubkey === pkpInfo.publicKey); // true
console.log(recoveredAddress === pkpInfo.ethAddress); // true

Learn More

By now you should have successfully signed data using a Lit PKP. If you’d like to learn more about what’s possible with Lit's PKP signing, visit the Programmable Key Pairs section.