Signing a Message
This guide covers the signMessageWithEncryptedKey
function from the Wrapped Keys SDK. For an overview of what a Wrapped Key is and what can be done with it, please go here.
Using the signMessageWithEncryptedKey
function, you can sign an arbitrary message using a Wrapped Key. The Wrapped Keys SDK will look up the corresponding encryption metadata (ciphertext
and dataToEncryptHash
) for your PKP in Lit's private DynamoDB instance. If found, it well then use your provided PKP Session Signatures to authorize decryption of the private key, and will sign your provided message, returning the signed message.
Below we will walk through an implementation of signMessageWithEncryptedKey
. The full code implementation can be found here.
Overview of How it Works
- The Wrapped Keys SDK will use the provided Wrapped Key ID and PKP Session Signatures to fetch the encryption metadata for a specific Wrapped Key
- Using the PKP Session Signatures, the SDK will make a request to the Lit network to execute the Sign Message Lit Action
- Depending on the provided
network
, one of the following Lit Actions will be executed:- If
network
isethereum
, then the signMessageWithEthereumEncryptedKey Lit Action is executed - If
network
issolana
, then the signMessageWithSolanaEncryptedKey Lit Action is executed
- If
- Depending on the provided
- The Lit Action will check the Access Control Conditions the plaintext private key was encrypted with to verify the PKP is authorized to decrypt the private key
- If authorized, the Wrapped Key will be decrypted within a Lit node's TEE. If not authorized, an error will be returned
- The Lit Action will use the decrypted Wrapped Key and the provided SignMessageWithEncryptedKeyParams to sign the arbitrary message, returning the signed message
Prerequisites
Before continuing with this guide, you should have an understanding of:
signMessageWithEncryptedKey
's Interface
/**
* Signs a message inside the Lit Action using the previously persisted wrapped key associated with the current LIT PK.
* This method fetches the encrypted key from the wrapped keys service, then executes a Lit Action that decrypts the key inside the LIT action and uses
* the decrypted key to sign the provided transaction
*/
async function signMessageWithEncryptedKey(
params: {
pkpSessionSigs: SessionSigsMap;
litNodeClient: ILitNodeClient;
network: 'evm' | 'solana';
id: string;
messageToSign: string | Uint8Array;
}
): Promise<string>
Parameters
pkpSessionSigs
When a Wrapped Key is generated, it's encrypted with the following Access Control Conditions:
[
{
contractAddress: '',
standardContractType: '',
chain: CHAIN_ETHEREUM,
method: '',
parameters: [':userAddress'],
returnValueTest: {
comparator: '=',
value: pkpAddress,
},
},
];
where pkpAddress
is the addressed derived from the pkpSessionSigs
. This restricts the decryption of the Wrapped Key to only those whom can generate valid Authentication Signatures from the PKP which generated the Wrapped Key.
A valid pkpSessionSigs
object can be obtained using the getPkpSessionSigs helper method available on an instance of LitNodeClient. We dive deeper into obtaining a pkpSessionSigs
using getPkpSessionSigs
in the Generating PKP Session Signatures section of this guide.
litNodeClient
This is an instance of the LitNodeClient that is connected to a Lit network.
network
This parameter dictates what message signing Lit Action is used to sign messageToSign
. It must be one of the supported Wrapped Keys Networks which currently consists of:
evm
This will use the signMessageWithEthereumEncryptedKey Lit Action.- Use this network if your Wrapped Key is a private key derived from the ECDSA curve.
- Uses Ethers.js' signMessage function to sign
messageToSign
.
solana
This will use the signMessageWithSolanaEncryptedKey Lit Action.- Use this network if your Wrapped Key is a private key derived from the Ed25519 curve.
- Uses the @solana/web3.js package to create a signer using the decrypted Wrapped Key, and the tweetnacl package to sign
messageToSign
.
id
This is a unique identifier (UUID v4) generated by Lit for the Wrapped Key.
Because a PKP can have multiple Wrapped Keys attached to it, this ID is used to identify which Wrapped Key to use when calling other Wrapped Key methods such as signMessageWithEncryptedKey and signTransactionWithEncryptedKey.
messageToSign
This parameter is the message, provided as either a string
or a Uint8Array
, that you would like the Wrapped Key to sign.
Return Value
signMessageWithEncryptedKey
will return the signature as Promise<string>
after it successfully decrypts and signs the provided messageToSign
using the Wrapped Key associated with the PKP derived from pkpSessionSigs
.
Example Implementation
Now that we know what the signMessageWithEncryptedKey
function does, it's parameters, and it's return values, let's now dig into a complete implementation.
The full code implementation can be found here.
Installing the Required Dependencies
- npm
- yarn
npm install \
@lit-protocol/auth-helpers \
@lit-protocol/constants \
@lit-protocol/lit-auth-client \
@lit-protocol/lit-node-client \
@lit-protocol/wrapped-keys \
ethers@v5
yarn add \
@lit-protocol/auth-helpers \
@lit-protocol/constants \
@lit-protocol/lit-auth-client \
@lit-protocol/lit-node-client \
@lit-protocol/wrapped-keys \
ethers@v5
Instantiating an Ethers Signer
The ETHEREUM_PRIVATE_KEY
environment variable is required. The corresponding Ethereum address needs to have ownership of the PKP we will be using to generate the pkpSessionSigs
.
import * as ethers from 'ethers';
import { LIT_RPC } from "@lit-protocol/constants";
const ethersSigner = new ethers.Wallet(
process.env.ETHEREUM_PRIVATE_KEY,
new ethers.providers.JsonRpcProvider(LIT_RPC.CHRONICLE_YELLOWSTONE)
);
Instantiating a LitNodeClient
Here we are instantiating an instance of LitNodeClient
and connecting it to the datil-dev
Lit network.
import { LitNodeClient } from "@lit-protocol/lit-node-client";
import { LitNetwork } from "@lit-protocol/constants";
const litNodeClient = new LitNodeClient({
litNetwork: LitNetwork.DatilDev,
debug: false,
});
await litNodeClient.connect();
Generating PKP Session Signatures
The LIT_PKP_PUBLIC_KEY
environment variable is required. This PKP should be owned by the corresponding Ethereum address for the ETHEREUM_PRIVATE_KEY
environment variable.
The PKP's Ethereum address will be used for the Access Control Conditions used to encrypt the generated private key, and by default, will be the only entity able to authorize decryption of the private key.
The expiration
used for the Auth Method must be 10 minutes or less to be valid.
The Auth Method used in this example implementation is signing a Sign in With Ethereum (EIP-4361) message using an Externally Owned Account (EOA), but any Auth Method can be used to authenticate with Lit to get PKP Session Signatures.
import { EthWalletProvider } from "@lit-protocol/lit-auth-client";
import {
LitAbility,
LitActionResource,
LitPKPResource,
} from "@lit-protocol/auth-helpers";
const pkpSessionSigs = await litNodeClient.getPkpSessionSigs({
pkpPublicKey: process.env.LIT_PKP_PUBLIC_KEY,
authMethods: [
await EthWalletProvider.authenticate({
signer: ethersSigner,
litNodeClient,
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
}),
],
resourceAbilityRequests: [
{
resource: new LitActionResource("*"),
ability: LitAbility.LitActionExecution,
},
],
expiration: new Date(Date.now() + 1000 * 60 * 10).toISOString(), // 10 minutes
});
Signing a Message With A Wrapped Key
Now that we have all that we need, we can call signMessageWithEncryptedKey
to sign a message with a Wrapped Key:
- Signing for an EVM Based Network
- Signing for Solana
import { api } from "@lit-protocol/wrapped-keys";
const { importPrivateKey } = api;
const signature = await signMessageWithEncryptedKey({
pkpSessionSigs,
litNodeClient,
network: 'evm',
id: process.env.WRAPPED_KEY_ID,
messageToSign: "The answer to the Universe is 42.",
});
import { api } from "@lit-protocol/wrapped-keys";
const { importPrivateKey } = api;
const signature = await signMessageWithEncryptedKey({
pkpSessionSigs,
litNodeClient,
network: 'solana',
id: process.env.WRAPPED_KEY_ID,
messageToSign: "The answer to the Universe is 42.",
});
Summary
The full code implementation can be found here.
After executing the example implementation above, you will have a signed message using the Wrapped Key that's associated with PKP derived from the provided pkpSessionSigs
.