Skip to content
Lugano Labs Discuss a project

Can a zero-knowledge proof show that an AI agent’s record follows a policy?

An auditor wants proof that an agent used only approved tools, sent nothing outside and made at most three payments. A zero-knowledge proof can show that the agent’s signed record says so, and keep it sealed. It can’t show that the record is true.

In short. Our draft design lets the holder of a signed AI agent record prove it meets a policy without showing it; our prototype did so for 1,000 entries with an 11,784-byte proof. That suits audits where the policy comes late, though the proof covers only what the issuer wrote down. An outside audit that found a flaw, or a fair rerun bringing a general zkVM within ten times of us, would change our view.

What this is. Findings from our draft paper, Sealed Receipts v0.5, published for public comment, and from the repository released with it. We built the design, the prover and the zkVM comparison. Nobody outside Lugano Labs has audited them.

Contents

A signed record shows where it came from, but checking it exposes everything

Here is a hypothetical case from our paper. A finance team runs a payroll agent. Its tool calls pass through a gateway run by a separate provider. For each run, the gateway signs a record of what passed through it and adds the record to a log. Months later, an outside auditor asks the team to show that each run used only approved tools, sent nothing outside and made at most three payments. [1]

With a plain signed record, the team has one way to answer. It hands the record over. The record lists every tool, destination, file and payment as a digest, a fixed-length fingerprint of the item. To learn any one fact, the auditor reads the whole inventory of private work. [1]

Our earlier format, Signed Agent Receipt v0.2, works this way. Our report on that format covers what its check proves and what it doesn’t. [1] [21]

So we asked a narrower question. Can the holder of such a record prove that it meets a policy the auditor picks, and keep the record hidden? Our paper, Sealed Receipts, answers with a design and a working prototype. It is draft v0.5, published for public comment on 10 November 2026. [1] [2]

The paper first names a simpler answer. The gateway could sign one more statement, saying that the record meets the policy. Sealed receipts earn their cost only when the auditor picks the policy later, the issuer is gone or unwilling, or the issuer shouldn’t learn who checks against which policy. [1]

The proof stayed at 11,784 bytes from 10 to 1,000 entries

The answer is yes, for one fixed kind of policy: a list of approved tools, a yes-or-no rule on sending data outside, and a cap on the number of payments. The holder proves three things without showing the record. A trusted issuer signed it. It sits in a signed log. And it meets the policy the verifier chose. The paper’s security argument is a sketch, not a full proof. [1] [17]

Our prover checks the record one entry at a time and folds the steps into one short proof. Folding merges each step’s check into a single running claim, so the final proof is built to stay one size however long the record is. The prover uses Nova, a folding scheme, over the Pallas and Vesta curves, with a Spartan compression step at the end. It needs no trusted setup. [1] [24]

We measured it on one Apple M5 Max, using the CPU only. Each figure in this table is the median of three runs, each in a fresh process. Three runs say little about how much one run differs from the next. [1] [5]

Our prover on one Apple M5 Max CPU, medians of three runs
Record sizeProve (s)Verify (ms)Proof (bytes)
10 entries4.87632259.95011,784
100 entries15.29733364.20411,784
1,000 entries94.92589263.89311,784

The verify column covers the proof check alone. A verifier that starts fresh must first load a 64.6 MB key, which takes about 6.2 s. The whole verify command took about 6.5 s on a 10-entry record, as the median of ten runs on a machine in use. A verifier that stays running loads the key once. [1] [3]

The paper defines two signature profiles, and we built one, profile b. In it the issuer signs with a Schnorr signature over Pallas, a curve whose arithmetic the proof can do directly. The log’s head is signed with Ed25519 and checked outside the proof. Profile a checks an Ed25519 signature inside the proof. We didn’t build it, so we have no figures for it. [1]

The prototype isn’t the cheapest form of the design. The paper describes a version that pays for each circuit only when it runs. Whether that version is secure on a pair of curves like ours is an open question. Our prototype puts all three parts in one circuit of 60,301 application constraints and pays for every part on every step. The part that checks the signature and the log is about three quarters of that circuit. We expect these timings to be an upper bound for the cheaper version, which we haven’t built. [1] [5]

A general-purpose zkVM took more than 160 times as long on the CPU

The obvious alternative is a zkVM, a system that can prove any program, running the same check. We wrote that check for the RISC Zero zkVM, version 3.0.6, and ran it on the same machine, also on the CPU only. [1] [4]

Before measuring, we made the baseline faster twice. We built the hash constants into the program instead of deriving them on every run, and we moved the signature arithmetic onto RISC Zero’s built-in accelerators. At 10 entries, those changes cut its cycle count from 33,685,504 to 20,971,520, or 37.7%. At 1,000 entries the cut was 1.3%. [2] [20]

The table compares RISC Zero’s default composite mode with our prover. The factor is RISC Zero’s time divided by ours. Neither time includes setup. Ours took between 4.17 and 4.28 s, and RISC Zero’s wasn’t measured separately. [1] [2] [7] [8]

Proving time on one Apple M5 Max CPU
Record sizeRISC Zero (s)RISC Zero runsOurs (s)Factor
10 entries795.330Median of three4.876322163
100 entries3,311.618One15.297333216
1,000 entries29,316.134One, on a machine in use94.925892About 308

At 1,000 entries, RISC Zero ran once, for about 8.1 hours, on a machine that was also in use. We sampled the load every five minutes. Over the 97 samples after launch, the prover held a mean of 1,620% of the CPU out of 1,800%. Other programs took a mean of 102%, with a peak of 706%. [1] [9] [10]

Shared load slows RISC Zero, so that run favours us by an amount we can’t state. We had stopped an earlier attempt after 47 minutes, because another job was using a large share of the CPU. It produced no proof and no timing. One run can’t bound the effect of the load. Still, we don’t expect the load to change the verdict. The 100-entry run, made without that load, already gives a factor of about 216. We made every run but the 1,000-entry one with no other heavy work on the machine, though we kept no load log for them. [1]

Read the factors with five more limits in mind. [1]

  • The two provers ran test records of the same length but not the same content. Ours repeat one approved tool and use a log of 16 leaves. RISC Zero’s use 3, 25 and 250 distinct tools and logs of 13, 103 and 1,003 leaves. The payment caps differ too. Ours equals the number of entries, and RISC Zero’s is the payment count plus one. [1] [4]
  • The RISC Zero program is stricter on one point. It rebuilds the record’s hash chain from the signed task binding, the value that ties a record to its task, and our circuit doesn’t. A test that runs both checks on the same small records found one case that our circuit accepts and the stricter check rejects. [1] [18]
  • The RISC Zero program covers the policy check, not the whole receipt protocol. It leaves out folding, the outer check of the log’s signed head, replay storage and the full wire format. The two sides also encode their proofs differently, and their verify steps don’t cover the same things. [1] [4]
  • Everything ran on the CPU. RISC Zero 3.0.6 disables its Apple GPU path in its source. Our build also replaced the unused Metal file with an empty one, because the machine lacked Apple’s Metal build tools. We didn’t test a CUDA GPU. We expect a GPU to cut RISC Zero’s time substantially. These measurements can’t say by how much. [1]
  • RISC Zero proves any program, and ours proves one fixed check. Part of the gap is the price of that generality. [1]

Our prover doesn’t win every measure. RISC Zero’s succinct receipt, measured at 10 and 100 entries, verifies in about 9 ms against our roughly 64 ms. It is 225,018 bytes against our 11,784. Among our own baselines, a single proof over the whole run is smaller than ours, at 7,928 bytes for 1,000 entries, and proving entry by entry uses less peak memory. The appendix has the timing and size tables. Memory and cycle counts are in the paper. Proof sizes leave out verification keys and the transport of public inputs. [1]

Two hypotheses held, and the third couldn’t be tested as written

We wrote the experiment design and three hypotheses before we measured. That is our own account. We didn’t register them with a third party, and the repository’s history starts after every measurement but the 1,000-entry RISC Zero run. The design also named the cheaper folding version and a cost report for profile a. We built neither. [1]

  • H1 said proof size and verification time would stay flat from 10 to 1,000 entries. Growth with the record would have refuted it. The proof was 11,784 bytes at all three sizes. Median verification stayed between 59.950 and 64.204 ms, and the highest median is about 7% above the lowest. In the raw results, single runs took from 58.076 to 186.134 ms. We set no tolerance for “flat” in advance. Our hypothesis also said that a failure would mean an implementation error. That was too strong, because timing noise alone could break flatness. The measurements support H1 for these fixtures on this machine. [1] [2] [6]
  • H2 said our prover would be at least ten times faster than the zkVM at 1,000 entries, on the same relation. A smaller factor would have refuted it, and the paper committed in advance to saying that the case for a purpose-built prover would then be weak. Every measured factor exceeds ten. The measurements support H2, but for a narrower test than it names, with the limits in the previous section. [1]
  • H3 said the final circuit would dominate cost for short runs and the step circuit for long ones. Our prototype runs every part of the circuit on every step, so it has no separate final circuit to time. H3 can’t be tested as written, and we haven’t rewritten it after the fact. [1]

The nearest measured quantity compares folding with the final compression step. Compression stayed between 3.375707 and 3.805185 s while folding grew. Folding cost less than compression at 16 folds and more at 128. By the paper’s arithmetic on the medians, the two would cross at about 37 to 43 folds, in a bucket we didn’t measure. [1] [5]

Some claims are measured, and others are only argued

The paper ends with a table that gives each claim a status. Here is a short form of it. “Measured” means we ran it and publish the raw results. “Argued” means the paper gives a reason but no proof and no outside audit. [1]

Status of each main claim, from the paper’s claims table
ClaimStatusMain limit
A proof can’t leave out or reorder entriesArgued, and exercised by testsNot audited outside Lugano Labs
Proof size and verify time stay flat as the record growsMeasured at 10, 100 and 1,000 entriesOne machine, CPU only, three runs each
The proof hides the recordBuilt with the proof library’s zero-knowledge foldingNot audited; the verifier still learns the public inputs and a size range
A replay within one scope shows upBuiltBest effort; a receipt issued again under a new nullifier key shows no repeat
Nullifiers from different scopes can’t be linkedArgued, under an assumption about the hash functionWhole presentations may still be linked
Audit mode shows that every receipt of an account was presentedArguedTrusts the issuer to log every receipt
Faster than a general zkVM on this checkMeasured at 10, 100 and 1,000 entriesNot one shared input; the 1,000-entry figure is one run, on a machine in use; CPU only
No trusted setupInherited from Nova and SpartanWe didn’t audit the library

A pass covers the record as the issuer wrote it, and nothing more

A sealed receipt proves only that some record, signed by a trusted issuer and included in a signed log, meets the stated policy. That leaves out a great deal. [1]

  • It doesn’t prove that the agent ran, that a digest matches a real file, that the record is complete, that data was kept or deleted, or what time it was. [1]
  • The issuer labels each entry’s kind. An issuer that files data sent outside as a file entry makes “nothing sent outside” pass. [1]
  • The policy counts payments. It can’t cap their value, check payees or bound time. [1]
  • Normally the holder chooses which receipts to show. The paper’s audit mode closes that gap. The issuer logs every receipt for an account in a log the holder doesn’t run, and the holder must then prove every receipt in that log. The auditor learns how many records the account has and whose they are, but not what they say. Audit mode still trusts the issuer to log every receipt. [1]
  • The log has its own signer. If that signer is lazy or curious, it can shrink the set of records a proof might be about, down to one. The paper doesn’t solve this. And when a holder asks the log for a membership path, the log learns which receipt that holder controls, so the paper tells holders to fetch paths early and in batches. [1]

Privacy has limits too. Each answer tells the verifier one bit, that this record meets this policy, and a refusal can tell it something as well. An auditor free to pick any policy could find a record’s exact payment count with about 32 questions. The verifier also learns the number of proof steps. The paper tells holders to pad each record to the next power of two, so the verifier learns a size range and not the exact length. Zero knowledge hides the record. It doesn’t promise that many records fit what is public. If only a few records match the public information, the verifier can narrow the proof down to one of them. [1]

The specification makes the holder’s software limit what it answers, with approved policy lists, policies committed in advance and a budget per receipt. The reference code has no such software, so none of those controls is built or tested. [1] [17]

Each proof also carries a nullifier, a value that repeats if the same receipt is shown twice in one scope. The verifier picks the scope, for example one audit. A verifier that keeps these values can spot a repeat. Verifiers that share a scope also learn when the same receipt went to each of them. A repeat is an alarm, not proof of fraud, because the holder may have had a good reason to show the record again. [1]

Under the paper’s assumptions, nullifiers from different scopes can’t be linked. Timing, a rare policy, a small set of matching records or colluding verifiers may still link two presentations. Replay detection is best effort, and it depends on the issuer. The value comes from a nullifier key, a secret the holder picks for each receipt. If the issuer signs the same task again under a new nullifier key, the second receipt gives a new value and no repeat shows. The design can’t detect that, though the audit mode at least makes the extra receipt visible. [1]

The proof library crashes on some malformed proofs, and our verifier catches it

One finding isn’t in the paper. It came from hardening our own verifier, and the evidence is in the repository and in our report to the library’s maintainers. The proof library we build on, nova-snark 0.76.0, can panic inside its verify function. It happens on a proof that decodes without error but holds a vector of the wrong length. A panic stops the running thread instead of returning an error. [12] [13]

Our test made one real proof and then changed each list inside it, one at a time. It emptied the list, removed its first item or copied its first item. Of 345 changed proofs, 339 still decoded. The library’s verify panicked on 101 of them, 95 at one place and 6 at another. It rejected the other 238 with an error, and it accepted none. [11] [13]

This matters to any service that checks proofs from people it doesn’t trust. One bad proof stops the thread, and it stops the whole process if the program is built to abort on a panic. Our verifier now catches the panic and reports a rejection. Our security notes warn against building it to abort. [12] [13] [14]

We reported both places to the library’s maintainers on 10 November 2026, as issue 519 in the microsoft/Nova repository. The report gives a short test that triggers the first panic with the library’s own types alone. [13]

One caution if you rerun our test. At v0.5 it calls our guarded verifier, so it counts those panics as rejections. It checks only that no panic escapes and no changed proof is accepted. The counts above aren’t in the paper or in the repository. They come from the run in our upstream report. [11] [12] [13]

We’d reach for sealed receipts only when the policy comes late

On our reading, the evidence supports a narrow claim. For long agent records and a fixed policy shape, a purpose-built folding prover can give a small proof that checks quickly. On the CPU, it beat a general zkVM by a wide margin. We wouldn’t extend that to GPUs, to other record mixes or to a quiet rerun at 1,000 entries until someone measures them. [1]

If a simpler tool fits, we’d start there. If the issuer is online, the policy is known when the record is made and the checks are routine, the issuer can sign a statement that the record complies. If you need to know what the agent actually did, not what the issuer wrote down, you need attested execution, such as a trusted execution environment. If the policy needs amounts, payees or arbitrary logic, a general zkVM can express it today, and our fixed policy shape can’t. [1]

Sealed receipts aim at a narrower case. That means long records, policies chosen late or kept private, an issuer that may be gone, and verifiers who shouldn’t see the content. [1]

Three results would change our view. An outside audit that found a soundness or zero-knowledge flaw would undo the result. A quiet rerun at 1,000 entries, a run on one shared input or a GPU run that brought RISC Zero within ten times of our prover would undercut H2. And a second implementation that disagreed with our test vectors, which so far come only from our own code, would show the format needs work. [1] [2]

Scope and limits

What we did
We specified the design, built a prover for profile b, one of its two signature profiles, wrote the same check for RISC Zero 3.0.6, and measured both on one Apple M5 Max, CPU only, at 10, 100 and 1,000 entries. An internal security review then added implementation checks.
What we didn’t do
Nobody outside Lugano Labs has audited the construction, the prover or the comparison, and we built both sides of it. We didn’t build the cheaper SuperNova design or profile a, and we didn’t test a GPU. We didn’t rerun the benchmarks after the hardening changes.
What this can’t tell you
Whether an agent did what its record says, or whether the record is complete. Whether the factors hold on other hardware, on one shared input or on a quiet machine at 1,000 entries, where RISC Zero ran once on a machine in use. Anything about anonymity: the fixtures are synthetic and their seeds are public.
Confidence
High that the benchmark figures here match the published raw files. Moderate on the size of the zkVM gap, since RISC Zero ran once at 100 entries and once, on a machine in use, at 1,000. Lower on security: the argument is a sketch, not a proof, and the code is unaudited.
What would change our view
An outside audit that finds a soundness or zero-knowledge flaw. A quiet rerun, a shared-input run or a GPU run that brings RISC Zero within ten times of our prover at 1,000 entries.
Errors
When we find an error, we fix it on this page and update its revision date. If you spot one, tell us through a research enquiry.

Appendix

How to repeat the measurements

All measurements used one Apple M5 Max with 18 CPU cores and 128 GB of memory, on macOS, with the CPU only. Our prover used Rust 1.94.0, nova-snark 0.76.0 and bincode 2.0.1. The baseline used RISC Zero 3.0.6. [1] [5]

Each of our configurations ran three times, each in a fresh process. We report the median of each metric on its own, so component medians need not add up to the total. RISC Zero ran three times per mode at 10 entries and once per mode at 100. At 1,000 entries it ran once, in composite mode only, on a machine in use. [1] [7] [8]

The published code is the v0.5 tag, commit 648975a. It isn’t the exact code we timed, and the changes are listed below. On Apple Silicon, these commands run the fast tests, the seven tests that make and check real proofs, and the benchmark, from code/zk-agent-receipt. [2] [3]

  • cargo test --target aarch64-apple-darwin -j 8 --workspace
  • RAYON_NUM_THREADS=8 cargo test --release --target aarch64-apple-darwin -j 8 -p zkar-prover -p zkar-cli -- --ignored --test-threads=1 --nocapture
  • cargo run --release --target aarch64-apple-darwin -p zkar-bench -- --t 10,100,1000 --reps 3 --out bench-results/my-run

The fast suite has 156 tests, and the repository’s automated checks run it with the lint. The seven real-proof tests need a release build and several minutes, so a separate job runs them only when someone starts it by hand. The RISC Zero commands are in the baseline’s README. [2] [3] [22] [4]

The raw data is public. Our prover’s results.md and results.json sit beside the output of every run. RISC Zero has one record per proof run, and the 1,000-entry run has its own record, run details and load log. [5] [6] [7] [8] [9] [10]

The benchmark run recorded dependency versions and raw logs, but no source revision. Between the measured runs and the tag, these things changed. [1] [2]

  • We made the file paths in the RISC Zero records relative. No measured value changed. [2]
  • We left out the RISC Zero receipt files, which range from 0.2 to 201 MB. The raw records keep their sizes and timings. [2]
  • We removed the names of other programs from the 1,000-entry load log. The CPU figures are unchanged. [2]
  • We edited comments, documents and the name of one test file, with no change to prover or relation logic. [2]
  • After the first release, an internal security review led to small hardening changes. The verifier now turns a library panic into a rejection and checks the expected policy, scope and issuer before the proof. The prover refuses a run too long for the format, the replay file is flushed to disk, and the command line returns 64 for more kinds of bad input. These add a few checks to code the benchmarks time. We expect them to be cheap, but we didn’t time them, and we didn’t run the benchmarks again. [2]

The same review added a test that feeds the proof and key decoders 1,537 malformed byte streams of up to 64 KiB each. No decoder panicked, and none held more than 2,105,349 bytes at one time. Malformed key files above 64 KiB weren’t tested. These checks don’t amount to an external audit. [1] [19]

All measured results

Our prover and its two direct baselines, as medians of three fresh-process runs. Setup is separate from proving. [1] [5]

Our prover and its baselines on one Apple M5 Max CPU
SystemEntriesSetup (s)Prove (s)Verify (ms)Proof (bytes)
Folded (ours)104.1722604.87632259.95011,784
Folded (ours)1004.21782115.29733364.20411,784
Folded (ours)1,0004.27709794.92589263.89311,784
One proof per run106.0777166.666265103.5446,056
One proof per run10057.84160147.909980496.2396,992
One proof per run1,000405.149895410.4844304,917.6437,928
One proof per entry101.7804286.365334171.85453,512
One proof per entry1002.09391048.8584841,403.516486,232
One proof per entry1,0002.246029510.51359113,575.1534,813,432

Peak memory for our prover was between 673,251,328 and 797,360,128 bytes. The single proof over a 1,000-entry run peaked at 70,747,422,720 bytes, about 70.7 GB. Neither baseline has the zero-knowledge step or the privacy padding of our prover. The per-entry baseline also lacks authenticated chaining, so it costs less than a secure design would. [1] [5]

RISC Zero, by mode. At 10 entries each value is the median of three runs, and at 100 it is one run. At 1,000 entries the composite row is one run on a machine in use, and the succinct time is a straight-line estimate from cycle counts, not a measurement. RISC Zero’s verification ran after proving in the same process, so it may have gained from warm caches. [1] [7] [8]

RISC Zero 3.0.6 on the same CPU
Record sizeModeProve (s)Verify (ms)Receipt (bytes)
10 entriesComposite795.330032625223.1408335,628,192
10 entriesSuccinct955.4123040428.975834225,018
100 entriesComposite3,311.618415375879.66733322,225,718
100 entriesSuccinct4,087.6437004599.063584225,018
1,000 entriesComposite29,316.1347187097,674.066292201,128,174
1,000 entriesSuccinct37,825.492 (estimate)Not measuredNot measured

The same straight-line method, applied before the composite run at 1,000 entries, predicted 30,415.029 s. The measured time came in 3.6% lower, even under shared load, so the succinct estimate may also be too high. [1]

Code, data, licence and how to cite

The paper, the SAR-ZK v0.4 profile, the reference prover, the RISC Zero baseline and every result file are public at Lugano-AI/zk-agent-receipt, tag v0.5, commit 648975a. They’re under the Apache License 2.0, and the copyright belongs to Lugano AI Holdings LLC. [2] [16]

The paper is draft v0.5 for public comment, dated 10 November 2026. The repository’s citation file asks you to cite it as Lugano Labs, “Sealed Receipts: Zero-Knowledge Policy Checks for AI Agent Task Records”, draft v0.5 for public comment, 2026, https://github.com/Lugano-AI/zk-agent-receipt. [1] [15]

To send a correction or a comment, open an issue on the repository. To report a security problem, follow its security policy, which asks for a private report, not a public issue, about any defect that doesn’t have a fix yet. [2] [14]

The open question

Does a purpose-built prover stay at least ten times faster than a general zkVM on a GPU, on a quiet machine and on one shared input?

The Sealed Receipts paper, draft v0.5, with its profile, reference prover, RISC Zero baseline and raw results, on GitHub under the Apache 2.0 license.

Discuss this question

References

A selected review of published sources, with Lugano’s analysis and proposed work identified in the text. Source dates and conditions matter; historical results are not current-model rankings.

  1. 1

    Sealed Receipts: Zero-Knowledge Policy Checks for AI Agent Task Records, draft v0.5

    Lugano Labs · 2026

    The paper this page reports: design, security sketch, hypotheses stated before measurement, method, results, threats to validity, limits and related work.

    Scope. Our own draft for public comment. Not peer reviewed and not independently audited; its security argument is a sketch, not a proof.Back to the essay

  2. 2

    Sealed Receipts repository README, v0.5

    Lugano Labs · 2026

    Summary of results, test commands, the changes made before publication and the hardening changes made after the benchmarks.

    Scope. Our own summary; it states that the benchmarks were not rerun after the hardening changes.Back to the essay

  3. 3

    zk-agent-receipt prover README, v0.5

    Lugano Labs · 2026

    Build and test commands, the whole-command verify timing, decoder limits and the note that the proof library can panic on malformed proofs.

    Scope. Describes our own unaudited reference code.Back to the essay

  4. 4

    RISC Zero SAR-ZK baseline README, v0.5

    Lugano Labs · 2026

    How the RISC Zero baseline implements the relation, its fixtures, its CPU-only build and its proving commands.

    Scope. We wrote this baseline ourselves; it omits folding, the outer log-head check, replay storage and the full wire protocol.Back to the essay

  5. 5

    Native benchmark results (results.md), 9 November 2026

    Lugano Labs · 2026

    Run metadata, exact crate versions and the median of every metric for the folded prover and both direct baselines.

    Scope. One machine, three runs per configuration, synthetic fixtures with a public seed; no source revision recorded.Back to the essay

  6. 6

    Native benchmark results (results.json), 9 November 2026

    Lugano Labs · 2026

    Every individual run behind the medians, including single-run verification times.

    Scope. Same run and limits as the results.md file.Back to the essay

  7. 7

    RISC Zero raw proof records at 10 and 100 entries

    Lugano Labs · 2026

    One JSON record per proof run: proving and verification time, receipt size, peak memory and cycle counts.

    Scope. Three runs per mode at 10 entries and one at 100; receipt files are not included.Back to the essay

  8. 8

    RISC Zero raw proof record at 1,000 entries

    Lugano Labs · 2026

    The single composite proof run at 1,000 entries: 29,316,134.718709 ms of proof generation and 7,674.066292 ms of verification.

    Scope. One run, on a machine in use; succinct mode was not proved at this size.Back to the essay

  9. 9

    Load log of the 1,000-entry RISC Zero run

    Lugano Labs · 2026

    CPU samples every five minutes for the prover and for all other processes during the run.

    Scope. Sampled with ps every five minutes; names of other programs were removed before publication.Back to the essay

  10. 10

    Run details of the 1,000-entry RISC Zero run

    Lugano Labs · 2026

    Start and end times, the command, environment, binary hash and the note that the machine was in use.

    Scope. Describes one run only.Back to the essay

  11. 11

    Malformed proof shapes test

    Lugano Labs · 2026

    The test that changes each array in a real proof and checks that the verifier rejects every change without a panic.

    Scope. At v0.5 it calls our guarded verifier, so it does not report the library panics by cause.Back to the essay

  12. 12

    Verifier source with the panic guard (proofs.rs)

    Lugano Labs · 2026

    The verifier wraps the library verify call in a panic guard and reports a panic as a malformed proof.

    Scope. Relies on the default unwinding panic strategy.Back to the essay

  13. 13

    Verifier panics on a proof that decodes but has a vector of the wrong length (issue 519)

    Lugano Labs, on the microsoft/Nova issue tracker · 2026

    Our report to the nova-snark maintainers, opened on 10 November 2026: the two panic sites, a minimal reproduction and the counts from our test.

    Scope. Our own report; it has no maintainer response.Back to the essay

  14. 14

    Security policy, v0.5

    Lugano Labs · 2026

    How to report a security problem, what is in scope, and the warning not to build the verifier to abort on a panic.

    Scope. States that the repository is research code, not for production use.Back to the essay

  15. 15

    Citation file, v0.5

    Lugano Labs · 2026

    The preferred citation for the paper and the release date of 10 November 2026.

    Scope. Citation metadata only.Back to the essay

  16. 16

    Apache License 2.0, v0.5

    Lugano Labs · 2026

    The licence that covers the repository.

    Scope. Licence text only.Back to the essay

  17. 17

    SAR-ZK v0.4-draft profile

    Lugano Labs · 2026

    The normative profile: what a pass establishes, the statement, the policy, the log, the nullifier and the wallet controls a holder must apply.

    Scope. A draft for public comment; not externally audited.Back to the essay

  18. 18

    Relation differential test

    Lugano Labs · 2026

    Gives the same records to our circuit constraints and to the plain relation the RISC Zero program runs, including the one case where they differ.

    Scope. Makes no proofs; covers the records it lists.Back to the essay

  19. 19

    Decoder robustness test

    Lugano Labs · 2026

    Feeds the proof and key decoders malformed byte streams and checks for panics and memory use.

    Scope. Byte streams of up to 64 KiB; larger malformed key files are not tested.Back to the essay

  20. 20

    Fairness fixes to the RISC Zero baseline

    Lugano Labs · 2026

    The two changes that made the baseline faster before measurement, with cycle counts before and after.

    Scope. Execution-only cycle counts; no proving was run for this comparison.Back to the essay

  21. 21

    Signed Agent Receipt v0.2

    Lugano Labs · 2026

    Our earlier signed receipt format and offline checker, which sealed receipts build on.

    Scope. A plain signed record: checking any fact requires the full record.Back to the essay

  22. 22

    Real-proof test workflow, v0.5

    Lugano Labs · 2026

    The job that runs the seven real-proof tests on a macOS arm64 runner when started by hand.

    Scope. Runs only on request, not on every push.Back to the essay

  23. 23

    zk-creds: Flexible Anonymous Credentials from zkSNARKs and Existing Identity Infrastructure

    M. Rosenberg, J. White, C. Garman and I. Miers · 2023

    The work our paper names as closest to sealed receipts.

    Scope. This page gives our paper’s description of it.Back to the essay

  24. 24

    Nova: Recursive Zero-Knowledge Arguments from Folding Schemes

    A. Kothapalli, S. Setty and I. Tzialla · 2022

    The folding scheme our prototype uses.

    Scope. This page gives our paper’s description of it.Back to the essay

  25. 25

    Revisiting the Nova Proof System on a Cycle of Curves

    W. Nguyen, D. Boneh and S. Setty · 2023

    The analysis that found a flaw in Nova’s original cycle-of-curves implementation and proved a fixed version secure.

    Scope. This page gives our paper’s description of it.Back to the essay

  26. 26

    Semaphore protocol documentation

    Privacy & Scaling Explorations · Undated

    The scoped nullifier pattern that sealed receipts follow.

    Scope. This page gives our paper’s description of it.Back to the essay

  27. 27

    ZK-Receipt: Privacy-Preserving Verifiable Provenance for Intermediated AI Services

    A. Emami Neishabouri, A. Sartipi, J. Schönrich and I. Tchappi · 2026

    A 2026 workshop paper on zero-knowledge proofs over provider-signed AI interaction records.

    Scope. This page gives our paper’s description of it.Back to the essay

  28. 28

    AgentProof: zero-knowledge verification of AI agent policy compliance

    AgentProof project · 2026

    An open-source proof of concept that proves a short tool-call log follows a small set of rules.

    Scope. This page gives our paper’s description of it.Back to the essay

  29. 29

    Authorization Without Disclosure, version 1.1

    A. Hasbini · 2026

    A proposed reference architecture for non-disclosing agent-to-agent authorization.

    Scope. This page gives our paper’s description of it.Back to the essay

  30. 30

    RISC Zero zkVM documentation

    RISC Zero · Undated

    The general-purpose zkVM used as the comparison baseline.

    Scope. General documentation; our measurements used version 3.0.6 on the CPU only.Back to the essay

Written by Lugano Labs. Contributors include Brian D. Evans.

About this report. First prepared 10 November 2026; revised 10 November 2026. This research report answers one question from primary sources.

Our theses, sources, interpretation and study designs may change with new evidence and are open to correction. Send a research enquiry.

Cite as. Lugano Labs. “Can a zero-knowledge proof show that an AI agent’s record follows a policy?” First prepared 10 November 2026; revised 10 November 2026. https://lugano.ai/research/sealed-receipts/

Download text

Lugano Labs Start a conversation

What would you like to work on?

A short note is enough. Please leave out credentials, secrets and sensitive source material.

What brings you here?

Choose one or more.