diff --git a/docs/docs.json b/docs/docs.json index df16c223a..0d7ed26bb 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -126,6 +126,12 @@ "sdk/resources/network-status" ] }, + { + "group": "Guides", + "pages": [ + "guides/lit-action-sign-as-action" + ] + }, { "group": "FAQ", "pages": [ diff --git a/docs/guides/lit-action-sign-as-action.mdx b/docs/guides/lit-action-sign-as-action.mdx new file mode 100644 index 000000000..ba6bb2ce2 --- /dev/null +++ b/docs/guides/lit-action-sign-as-action.mdx @@ -0,0 +1,71 @@ +--- +title: 'Derive Lit Action Public Keys' +description: 'How to deterministically derive and verify a Lit Action identity without executing it externally.' +--- + +# Derive a Lit Action Public Key Locally + +## Question + +I want to call `Lit.Actions.signAsAction`. I know the action identity is derived from the Action's IPFS CID, but I cannot find a way to obtain the public key outside of the Action runtime. `Lit.Actions.getActionPublicKey` works within the Action, while `executeJs` only exposes `signatures..publicKey` after a signing operation. Is there a way to deterministically derive the Action's public key locally without running the Action? + +## Answer + +Yes. Inside the Lit Action you can deterministically derive the Action identity (and therefore its public key) from the same inputs the nodes use: the Action's IPFS CID and the signing scheme. The snippet below shows the complete flow: + +1. Produce the 32-byte message hash the Lit nodes expect. +2. Call `Lit.Actions.signAsAction` to sign that message with the Action identity. +3. Derive the Action public key via `Lit.Actions.getActionPublicKey`, passing the Action CID and signing scheme. +4. Optionally verify the signature with `Lit.Actions.verifyActionSignature`. + +```js +const { sigName, toSign } = jsParams; // 'publicKey' not required; derive it from the Action IPFS CID +const { keccak256, arrayify } = ethers.utils; + +(async () => { + // 1) Produce a 32-byte hash of the input (Lit Actions expect a 32-byte message for ECDSA schemes) + const msgBytes = new TextEncoder().encode(toSign); + const msgHashHex = keccak256(msgBytes); // 0x-prefixed hex string + const msgHashBytes = arrayify(msgHashHex); // Uint8Array + + // 2) Sign as the current Lit Action (deterministic Action identity, not a PKP) + // Supported schemes include 'EcdsaK256Sha256' (secp256k1) among others. + const signingScheme = 'EcdsaK256Sha256'; + const signature = await Lit.Actions.signAsAction({ + toSign: msgHashBytes, + sigName, + signingScheme, + }); + + // 3) Derive this Action's public key deterministically from its IPFS CID + scheme + // This does not require a PKP and is always the same for a given (CID, scheme). + const actionIpfsCid = Lit.Auth.actionIpfsIdStack[0]; + const actionPublicKey = await Lit.Actions.getActionPublicKey({ + signingScheme, + actionIpfsCid, + }); + + // 4) (Optional) Verify that the signature was produced by this Action identity + const verified = await Lit.Actions.verifyActionSignature({ + signingScheme, + actionIpfsCid, + toSign: msgHashBytes, + signOutput: signature, + }); + + // 5) Return a structured response for clients to consume + Lit.Actions.setResponse({ + response: JSON.stringify({ + sigName, + signingScheme, + message: toSign, + messageHash: msgHashHex, + signature, // string; format depends on scheme + actionPublicKey, // string; hex or JSON depending on scheme + verified, // boolean + }), + }); +})(); +``` + +This approach keeps the derivation entirely within the Lit Action context. Because the public key depends only on the Action CID and signing scheme, you can rely on `Lit.Actions.getActionPublicKey` for a deterministic identity without needing to execute the Action externally first.