From Session Keys to Session Signatures: Part 2

From Session Keys to Session Signatures: Part 2

Please refer to part 1 for a high level overview of session keys and session signatures.

Unlocking Web3 UX with Session Signatures
Imagine a scenario where a user Alice owns a Rate Limit Increase (RLI) Non-Fungible Token (NFT) and a Programmable Key Pair (PKP), and wishes to delegate its use to another user, Bob, but only for specific Lit Actions or another resource she owns. This could be achieved through the use of session signatures, with Alice creating a session capability object and issuing it to Bob.

Introducing a session signature scheme that is based off Sign-In With Ethereum (SIWE) and session keys enhances web3 user experience. In this approach, users can delegate permissions to a session key, which we call a SessionSig. This method is comparable to logging into a service using cookie-based authentication, allowing users to interact with their storage without the need for continuous permission checks. Importantly, even if a session key is compromised, the potential damage is confined. By adhering to the SIWE ReCap standard this system ensures interoperability with all other compliant systems.

This article will walk you through some basic scenarios in greater detail, using concrete examples and code snippets.

Working With the Lit Network

To interact with the nodes in the Lit network, you will need to generate and present signatures. Currently, there are two ways to do so:

  1. Obtain an AuthSig

A wallet signature, also referred to as AuthSig, is a signature that proves you own a particular public key. Learn more about wallet signatures here.

  1. Generate SessionSigs (Recommended)

Session signatures, or SessionSigs, are signatures that are scoped to specific capabilities and resources. For example, you can set up SessionSigs to permit only the encryption and decryption of data during a particular time frame.

SessionSigs are designed to be ephemeral and limited in scope, allowing for fine-grained control and enabling secure, seamless interactions with any platform integrating Lit.

This article is focused on creating and using session signatures.

You can follow along with this video that gives a brief overview of Lit and goes through an example of using session signatures:

Developer documentation on session signatures

What are SessionSigs?

SessionSigs are produced by an ed25519 key-pair that is generated randomly on the browser and stored in local storage. The first step to producing SessionSigs is to first obtain an AuthSig through an authentication method like Google OAuth (example here). By specifying the session key-pair's public key in the signature payload of the AuthSig - the uri field of the SIWE - users can choose which specific actions to delegate to the session key-pair for operating upon certain resources.

The session key-pair is used to sign all requests to the Lit nodes, and the user's AuthSig is sent along with the request, attached as a capability to the session signature. Each node in the Lit network receives a unique signature for each request, and can verify that the user owns the wallet address that signed the capability.

Session signatures are composed of:

  • sig: the signature produced by the ed25519 keypair signing the signedMessage payload
  • derivedVia: should be litSessionSignViaNacl and specifies that the SessionSig object was created via the NaCl library.
  • signedMessage: the payload that was signed by the session key-pair.
  • address: the session key-pair public key.
  • algo: the signing algorithm used to generate the session signature.

An example of a session signature on Lit’s network looks like:

{
    "sig": "0196a7e5b8271e287fc376af3ae35955cac1009149b9b9eab4c5f8c845ca20658f937a42b7c03a8884573b801de1c36f9fa8a6d2f3ba432dc4326443c114c40c",
    "derivedVia": "litSessionSignViaNacl",
    "signedMessage": '{
        "sessionKey": "6a1f1e8a00b61867b85eaf329d6fdf855220ac3e32f44ec13e4db0dd303dea6a",
        "resourceAbilityRequest": [
            {
                "resource": "lit-accesscontrolcondition://524a697a410a417fb95a9f52d57cba5fa7c87b3acd3b408cf14560fa52691251",
                "ability": "access-control-condition-decryption"
            }
        ],
        "capabilities": [{
            "sig": "0xef8f88fb285c0065946f7257034226923e3bbf7c6c69f8863be213e50a1c1d7f18124eefdc595b4f50a0e242e8e132c5078dc3c52bda55376ba314e08da862e21a",
            "derivedVia": "web3.eth.personal.sign",
            "signedMessage": "localhost:3000 wants you to sign in with your Ethereum account:
                0x5259E44670053491E7b4FE4A120C70be1eAD646
                URI: lit:session:6a1f1e8a00b61867b85eaf329d6fdf855220ac3e32f44ec13e4db0dd303dea6a
                Version: 1
                Chain ID: 1
                Nonce: ZfYjGsNyaDDFlaftP
                Issued At: 2022-10-30T08:25:33.371Z
                Expiration Time: 2022-11-06T08:25:33.348Z
                Resources:
                - urn:recap:eyJhdHQiOnsibGl0LWFjY2Vzc2NvbnRyb2xjb25kaXRpb246Ly81MjRhNjk3YTQxMGE0MTdmYjk1YTlmNTJkNTdjYmE1ZmE3Yzg3YjNhY2QzYjQwOGNmMTQ1NjBmYTUyNjkxMjUxIjp7IiovKiI6W3t9XX19LCJwcmYiOltdfQo=",
            "address":"0x5259E44670053491E7b4FE4A120C70be1eAD646b"
        }],
        "issuedAt": "2022-10-30T08:27:01.667Z",
        "expiration": "2022-10-30T08:32:01.667Z",
        "nodeAddress": "<https://node2.litgateway.com:7370>"
    }',
    "address": "6a1f1e8a00b61867b85eaf329d6fdf855220ac3e32f44ec13e4db0dd303dea6a",
    "algo": "ed25519"
}

As we can see in the above session signature example, a bulk of the signature is comprised of the signed message.

The schema for signedMessage is:

  • sessionKey: the session key-pair public key.
  • resourceAbilityRequests: a list of abilities that the session key is requesting to perform against the specified Lit resources during authentication.
  • capabilities: an array of one or more AuthSigs.
  • issuedAt: is the time the session signature was issued.
  • expiration: is the time the session signature becomes invalid.
  • nodeAddress: is the specific URL the session signature is meant for.

Let's take a look into resources and abilities....

What are Lit Resources and Abilities?

Lit resources and abilities are used to specifying what action is to be done against which resource. These are primitives that are used to securely authenticate users through appropriate scoping of users' abilities across various resources.

Lit Resources

A Lit Resource refers to one of the following:

  • An access control condition
  • A Programmable Key Pair (PKP) NFT
  • A Rate Limit Increase (RLI) NFT
  • A Lit Action

Each Lit Resource is identified by its resource key:

  • For access control conditions, the resource key is derived from the hash of either the encrypted symmetric key or the JWT signing payload (resource ID)
  • For PKP NFTs, the resource key is the token ID of the NFT
  • For RLI NFTs, the resource key is the token ID of the NFT
  • For Lit Actions, the resource key is the IPFS Content ID (CID) of the Lit Action code

A wildcard resource key, identified by *, refers to all of the resources in that category, i.e. all of the PKP NFTs or all of the access control conditions.

Lit Abilities

A Lit Ability is an action to be performed. It can only be one of the following:

  • Threshold decryption from an access control condition
  • Threshold signing from an access control condition
  • Threshold signing with a PKP NFT
  • Authenticating with an increased rate limit threshold with a RLI NFT
  • Threshold signing of a piece of Lit Action code

Capability Objects

Session signatures work by having scoped capabilities be granted to session keys by an inner AuthSig. The capability object is a SIWE ReCap object.

When session capability objects are omitted from the getSessionSigs() function call, the SDK will generate a session capability object with wildcard permissions against all of the resources in that category by default, i.e. ability to perform operations against all access control conditions.

Authenticate with SessionSigs

Once you have obtained SessionSigs, you can replace where you provide an AuthSig with the SessionSigs object. Below are some examples using the Lit SDK.

For both examples, we will use the same access control condition where the account should have at least 0.00001 ETH.

var unifiedAccessControlConditions = [
  {
    conditionType: "evmBasic",
    contractAddress: "",
    standardContractType: "",
    chain: "ethereum",
    method: "eth_getBalance",
    parameters: [":userAddress", "latest"],
    returnValueTest: {
      comparator: ">=",
      value: "10000000000000",
    },
  },
];


Making Signing Requests

// Saving signing condition
await litNodeClient.saveSigningCondition({
  unifiedAccessControlConditions,
  sessionSigs,
  resourceId,
  chain: "litSessionSign",
});

// Retrieving a signature
let jwt = await litNodeClient.getSignedToken({
  unifiedAccessControlConditions,
  sessionSigs,
  resourceId,
});


Making Encryption Requests

Encryption


// encrypt
const { encryptedZip, symmetricKey } =
  await LitJsSdk.zipAndEncryptString("this is a secret message");

// store the decryption conditions
const encryptedSymmetricKey = await litNodeClient.saveEncryptionKey({
  unifiedAccessControlConditions,
  symmetricKey,
  sessionSigs,
});

// retrieving the key:
const hashOfKey = await LitJsSdk.hashEncryptionKey({
  encryptedSymmetricKey,
});

The LitAccessControlConditionResource(string: resource) is used to create an access control condition resource.

getSessionSigs: gets session signatures for a set of resources.

High level, how this works:

  1. Generate or retrieve the session key.
  2. Generate or retrieve the wallet signature of the session key.
  3. Sign the specific resources with the session key.
// Create an access control condition resource
var litResource = new LitAccessControlConditionResource(hashOfKey);

sessionSigs = await LitJsSdk.getSessionSigs({
  chain: "ethereum",
  litNodeClient,
  resourceAbilityRequests: [
    resource: litResource,
    ability: LitAbility.AccessControlConditionDecryption
  ]
});

Decryption


 const retrievedSymmKey = await litNodeClient.getEncryptionKey({
  unifiedAccessControlConditions,
  toDecrypt: LitJsSdk.uint8arrayToString(
    encryptedSymmetricKey,
    "base16"
  ),
  sessionSigs,
});

const decryptedFiles = await LitJsSdk.decryptZip(
  encryptedZip,
  retrievedSymmKey
);
const decryptedString = await decryptedFiles["string.txt"].async(
  "text"
);
console.log("decrypted string", decryptedString);

Example

Check out this example repository to see how to use and set up session signatures:

OAuth PKP Signup Example

This project lets you start using Ethereum with just a Google Account. It's a simple example of how to use the Lit Protocol Oauth service to authenticate users and then use their Ethereum address.

This project specifically:

  1. Uses Google oAuth to auth the user
  2. Mints a PKP token for the user, with their Google account as a valid auth method
  3. Uses the PKP token to get an Ethereum address for the user
  4. Generates a local session key for the user and stores it in LocalStorage
  5. Uses the Lit Protocol's PKP session signing service to sign that session key with their PKP
  6. Uses the local session key to sign a request to encrypt and decrypt a string that only the user can decrypt.

The Future of Session Signatures

As the digital landscape continues to evolve, session signatures will become even more integral to ensuring secure digital experiences. As cybersecurity threats continue to grow in scope and complexity, it is clear that session signatures, along with other security measures, will be at the forefront of defending against these threats. By understanding and implementing these technologies, we can work towards a more secure future.

In conclusion, session signatures are more than just a tool for secure communication—they can be a foundational element of our digital world.

Special thanks to Howard Tam for the examples and review.


Developer Documentation | Discord | GitHub | Twitter | Website