← All articles

How to show regulators exactly which code handled your data

Run the code in an attested enclave and have it sign each result with a key that belongs to that exact code. Here is how that works on Lit, and how a regulator can check a result without trusting you.

Lit Protocol

To show a regulator exactly which code handled a piece of data, run the code in an attested hardware enclave and have it sign every result with a key that belongs to that exact code. On Lit Protocol, a Lit Action is identified by a hash of its source, and every action has its own signing key that the enclave derives from that hash. If the code changes by one character, the hash changes and so does the key. So a signed result is a receipt that names the code that produced it, and anyone can check it later without trusting you.

I will build a small screening check that returns signed receipts, and then go through how a regulator verifies one.

A check that signs its own receipts

This action screens a customer against a sanctions service and returns a receipt. The receipt is signed with the action's own key.

// screen-v1.js
async function main({ customerId, name, country, salt }) {
  const result = await fetch("https://screening.example.com/check", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ name, country }),
  }).then(r => r.json());

  const receipt = JSON.stringify({
    customerId,
    inputHash: ethers.utils.id(`${salt}|${name}|${country}`), // ties the receipt to the data that was screened
    outcome: result.match ? "match" : "clear",
    listVersion: result.listVersion,
    checkedAt: new Date().toISOString(),
  });
  const signer = new ethers.Wallet(await Lit.Actions.getLitActionPrivateKey());
  return { receipt, signature: await signer.signMessage(receipt), signer: signer.address };
}

The screening URL stands in for whichever provider you use. The receipt leaves out the customer's name. It includes a hash of the name and country with a random salt that you keep, so you can store and share the receipt without the personal data, and later show which data was screened by revealing the salt.

Your service calls the action with a usage API key. Send the X-Privacy-Mode header so Lit's server does not write the request or the result to its logs.

SALT=$(openssl rand -hex 16)   # keep this with your private record for c-1042
jq -n --rawfile code screen-v1.js --arg salt "$SALT" '{code: $code, js_params: {customerId: "c-1042", name: "Jane Doe", country: "GB", salt: $salt}}' \
  | curl -s -X POST https://api.chipotle.litprotocol.com/core/v1/lit_action \
      -H "X-Api-Key: $USAGE_KEY" -H "X-Privacy-Mode: true" -H "Content-Type: application/json" -d @-

Store each receipt and signature wherever you keep audit records. A signed receipt cannot be edited without breaking the signature, so the storage itself does not have to be trusted.

How a regulator checks a receipt

You give the regulator the source file, screen-v1.js, and the receipts they want to look at. They can do the rest themselves.

First, they compute the code's hash. This call needs no account.

jq -Rs . screen-v1.js | curl -s -X POST https://api.chipotle.litprotocol.com/core/v1/get_lit_action_ipfs_id \
  -H "Content-Type: application/json" -d @-
# "Qm..."

Next, they look up the signing address for that hash. They can do this from their own Lit account by running a one-line action, Lit.Actions.getLitActionWalletAddress({ ipfsId }), with a usage key that is allowed to run it. The enclave derives the address from the hash, so every account gets the same answer. You can also publish the address next to the source, and the regulator can confirm it this way.

Then they check each receipt against that address.

import { ethers } from "ethers";
const recovered = ethers.verifyMessage(receipt, signature);
console.log(recovered.toLowerCase() === actionAddress.toLowerCase()); // true means screen-v1.js produced this receipt
// once you share the salt for a customer, this shows which data was screened
console.log(ethers.id(`${salt}|${name}|${country}`) === JSON.parse(receipt).inputHash);

That proves which code produced the receipt. To trust that the code ran where you say it did, the regulator also checks the enclave. Phala's Trust Center publishes a report for Lit's production enclave that verifies the Intel TDX hardware quote and the hash of the deployed server, and the verification guide explains each layer.

Changing the code

When you change the screening logic, you get a new file with a new hash and a new signing address. Old receipts still verify against the old address, and new ones verify against the new one. Keep every version of the source you have run, because each one is what a past receipt points to.

What this does not show

The receipt proves which code ran and what it returned. It does not show which account ran it, and it does not prove the screening provider gave a correct answer. Anyone can run a public action, so the input hash is what ties a receipt to your customer's data. A Lit Safe that needs 2 of its 4 signers approves the code that runs in Lit's enclave, and a release approved that way could sign with any action's key, so the receipt also depends on that approval process. The upgrade governance page explains how a new release is approved. If your regulator wants that decision with you, Lit is open source and you can run your own deployment.

Working with Lit

Lit works with regulated teams to set up checks that produce receipts like these. To talk about the data you handle and what your regulator needs to see, use the Lit contact form. Signing results with an action's own key is covered under patterns in the docs.