← Blog
How it works FundQuill

What a signed FundQuill report can and cannot prove

“I signed off on a model months ago and now I have to explain how I checked it.”

You reviewed the model in March. In September somebody asks what you actually checked, and whether the numbers in your summary came from the file as it stood at the time.

A screenshot does not answer that. Neither does a memory. This is what FundQuill puts in a report so the question has an answer, and, more usefully, what that answer does not cover.

The awkward part of simulating anything

If a report contains a Monte Carlo result, it has a credibility problem that a list of findings does not.

Run ten thousand trials today and you get one set of percentiles. Run ten thousand tomorrow and you get slightly different ones. Both are correct. Neither can be checked against the other, so quoting either in a document you expect somebody to rely on is awkward: a reviewer who re-runs it will not match your numbers, and cannot tell whether that is sampling noise or a changed model.

The fix is that the run has to be reproducible.

How the simulation is made reproducible

The sampler is SplitMix64, a small deterministic generator seeded by a single number. There is no external randomness source and no system entropy involved. The same engine build, the same model, and the same seed produce exactly the same trials, in the same order, with exactly the same output.

The seed and the trial count are recorded in the report. That is what turns a simulation from an anecdote into something a reviewer can re-run and match.

Two smaller details matter more than they sound.

Your model is not modified. A simulation overwrites the input cells for each trial, then puts the original values back before returning, including when a run stops early. Cells containing formulas are rejected up front, before anything is changed, rather than being overwritten and put back, because a restore that goes wrong on a formula cell would silently corrupt the model you are auditing.

Failed trials are counted, not dropped. For each watched output the report carries the number of trials that produced a finite number and, separately, the number that produced an error or a non-numeric result. Those error trials are excluded from the statistics but they are visible. A model that divides by zero in eight percent of scenarios is telling you something, and quietly averaging the other ninety-two percent would hide it.

Alongside those two counts the report carries the mean, standard deviation, min and max, and the 5th, 10th, 25th, 50th, 75th, 90th and 95th percentiles. The histogram you see on screen is left out of the report: it is a picture of the same samples, not a separate claim.

Which engine ran

"Same engine" needs to mean something precise, and a version number is not precise enough. Two builds can carry the same number and still behave differently.

So the report records two things. The version number comes from the engine's own build, not from a label someone has to remember to update. Next to it is the SHA-256 of the exact engine file that ran. The app downloads that file once, hashes those bytes, and starts the engine from the same bytes, so the hash describes what actually ran rather than what a URL happened to serve. Every release checks that the engine file it ships is byte for byte the one that was built, and prints the hash a report from that release will carry.

What the signature covers

The report contains a header describing the build (the tool, the format version, the engine version, the engine hash and the time the report was generated), followed by the file it was run against (identified by the SHA-256 of its exact bytes), the findings, the warnings, the assumptions you set, and the simulation result.

Each of those sections is hashed, and the hashes are chained, so the last hash in the chain depends on every byte of every section. The line telling a reviewer how to reproduce the run is built only from values inside the chain, and is checked against them, so it cannot be edited separately either. Change the engine version, the timestamp, or a single failed-trial count, and the chain no longer matches.

On the paid tier the end of that chain is signed with a key generated in your browser when you activate. The private half is created as non-extractable, which means not even the app's own code can read it out, so the key cannot be copied to another machine. The report names its signer by the fingerprint of the public key, and that fingerprint is computed from the key embedded in the report, never taken from a field the report merely claims. Anyone can sign an edited report with a key of their own, but it will show their fingerprint, not yours.

The message that gets signed is domain separated. It is not the bare hash but the hash prefixed with a report specific context string, and that is a deliberate piece of plumbing worth explaining. The same device key is involved in licensing. Without domain separation, a signature over a bare hash could potentially be replayed in the licensing flow as proof of possession of the key. Prefixing the message means a report signature is only ever valid as a report signature.

On the free tier you get the same document, unsigned and watermarked, and the watermark says so plainly: not for use as evidence. A free report is a sample of the format, not a record anyone should rely on. The audit findings are not withheld; the simulation and the signature are what Pro adds. What you pay for is the attestation, not the audit.

Reports generated before 28 October 2026 used an earlier format that signed the file, findings, warnings, assumptions and simulation, but not the build header. They still verify, and what they sign is still covered. Their engine version and timestamp are not.

How the person you send it to checks it

The recipient does not need FundQuill, a license, or your model to check a report. There are two ways, and both are tested against the same set of sample documents, with the same expected verdicts.

The first is fundquill.com/verify, a free page. Drop the report in and it tells you whether the contents are intact and which device key signed it. If you also have the model, drop that in too and the page confirms it is the exact file the report describes. The check runs in your browser and the page makes no network requests while it does it, so neither file is uploaded.

The second does not involve our website at all. The format is small enough to describe in a few lines: each section is hashed together with the hash before it, the last hash is signed with ECDSA P-256, and the device fingerprint is the SHA-256 of the public key. fundquill-check implements exactly that. It is open source under the MIT license, has no dependencies, and makes no network calls:

npx fundquill-check report.json model.xlsx --fingerprint <the sender's fingerprint>

The same repository publishes a single offline HTML file that does the check in a browser with no connection at all, with its SHA-256 listed so you can confirm you have the published copy.

Two things about what either check means. It confirms the record is intact and names the key that signed it. It does not re-run the engine: the numbers are shown as they were produced, and the engine hash tells you which build produced them. And the fingerprint only helps if you compare it with one the sender gives you some other way, a phone call or a message you already trust. Reading it off the report itself proves nothing about who sent it.

What this does not prove

It does not say the model is right. The signature covers a document describing what was found. Whether those findings are the ones that mattered is a question about the rules and about your judgment, and no signature speaks to it.

It attests a key, not a person. A signature proves which device key signed the report. It does not prove who holds that key, so check the fingerprint with the sender through a channel you already trust. Nor does it say the key belongs to a qualified reviewer, that the reviewer was diligent, or that anyone else agreed. If your process needs a third party to vouch for the work, this is not that, and no local tool can be.

Reproducibility is bounded by the engine build. Same seed and same build gives the same numbers. A later build is not obliged to, and the report records the hash of the build precisely so that a mismatch is visible rather than mysterious. Re-running an old report exactly needs that exact build. Builds are made by our release pipeline and old ones are not archived, and building the same source on a different machine does not give byte-identical output, so an old report can be re-run only while its build is still the one being served. Checking the record does not depend on that; re-running it does.

The timestamp is your computer's clock. The signature shows the time has not been edited since the report was signed. It does not show the clock was right when it was.

A signed report can still describe a model you misread. The tooling makes the record solid. It does not make the review good. Those are different problems and only one of them is ours.

What you get is narrower than the word "signed" usually implies, and it is the part that was actually missing: six months later you can say which file, which engine build, which assumptions, which seed, and show that the record has not moved since.