---
title: "Evidence: sign and verify assessment reports | Settlens"
description: "Settlens Evidence: generate an ed25519 key, attest a report, verify the attestation offline, the attestation format, key management and its limits."
url: https://settlens.com/docs/evidence
last_updated: 2026-10-13
---

# Evidence

> **In short:** Settlens Evidence signs a report's SHA-256 digest, summary and timestamp with your own ed25519 key (evidence keygen, then attest). Anyone verifies the attestation offline with evidence verify, and --expect-key pins the signer. It proves which key signed which exact report, not when the scan ran.

Bind a report's digest to an ed25519 signing key and a timestamp, so a partner or auditor can verify who vouched for it, offline.

Every Settlens JSON report carries a SHA-256 digest that shows whether it was modified (see [Integrity](https://settlens.com/docs/reports#integrity)). A digest alone does not say who produced the report: anyone who edits it can compute a new one. An evidence attestation closes that gap. It signs the report's digest, together with a summary and a timestamp, with an ed25519 key, producing a small JSON file that anyone can verify offline with the Settlens CLI or the MCP `verify_attestation` tool. Signing uses Node.js' built-in `node:crypto`: no network, no HSM and no key custody by Settlens.

_Diagram: The signer runs evidence attest with the report and a private key to produce attestation.json. Anyone verifies it offline with evidence verify, optionally with the report, and then compares the public key in the attestation with the key the signer published._

_Signing happens once, where the key is; verification needs only the files._

## Walkthrough

### 1. Generate a signing key

`keygen` creates an ed25519 key pair. With `--out` the private key is written as a PKCS#8 PEM file, created readable only by you (mode 0600), and the public key is printed. `keygen` never replaces an existing file: if the path exists it stops with exit code 2 and leaves the key untouched. Without `--out` the private key is printed to stdout, so avoid that anywhere output is logged.

**settlens evidence keygen**

```text
$ settlens evidence keygen --out settlens-evidence.key
Private key written to settlens-evidence.key (keep it secret).
Public key: 0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2
$ settlens evidence keygen --out settlens-evidence.key
Settlens error: settlens-evidence.key already exists. Refusing to overwrite a key file; choose another --out path.
```

Publish the public key where the people who will verify your attestations can find it and trust it: your website, your security page, a signed email, a contract annex.

### 2. Attest a report

Produce a JSON report with [`scan`](https://settlens.com/docs/cli#scan) or [`check`](https://settlens.com/docs/cli#check), then sign it:

**settlens evidence attest**

```text
$ settlens evidence attest reports/settlens-4b9f875a.json \
    --key settlens-evidence.key --out attestation.json
Attestation written to attestation.json
```

**attestation.json**

```json
{
  "kind": "settlens-evidence-attestation",
  "version": 1,
  "canonicalization": "settlens-stable-json-v2",
  "reportId": "4b9f875a",
  "productVersion": "0.5.0",
  "protocolVersion": 2,
  "subjects": [
    "http://127.0.0.1:4402/secure"
  ],
  "summary": {
    "pass": 13,
    "fail": 0,
    "warn": 0,
    "skip": 0
  },
  "reportDigest": "0a7f80cf394e699ccf0105793d3ec0255d8e0811f7634ca8adbffc1c0d19a91a",
  "attestedAt": "2026-10-13T11:11:25.166Z",
  "publicKey": "0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2",
  "signature": "0x63d62a18a3203174e1d6ab6dd561743e9a7da91f83084ef43f49d6a45faeda85bec0435cc385e23070fc34f71121bf1efde0a1510b39d4e885f2f9f502245b00"
}
```

Share the attestation together with the JSON report (and, for readers, the HTML report generated in the same run).

### 3. Verify, offline

The recipient needs only the files. With `--report`, Settlens also checks that the report is intact and is exactly the one that was signed:

**settlens evidence verify (exit 0)**

```text
$ settlens evidence verify attestation.json --report reports/settlens-4b9f875a.json
VALID evidence attestation
Signature valid: true
Report matches:  true
Public key:      0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2
```

Any change to the attestation breaks the signature, and a different report does not match the signed digest:

**Failed verifications (exit 1)**

```text
$ settlens evidence verify attestation-edited.json
INVALID evidence attestation
Signature valid: false
Public key:      0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2
Reasons:
  - The attestation signature is invalid.
$ settlens evidence verify attestation.json --report other/settlens-237f6e6f.json
INVALID evidence attestation
Signature valid: true
Report matches:  false
Public key:      0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2
Reasons:
  - The attestation does not match the provided report digest.
```

If the report itself was edited, verification fails with `The provided report failed its own integrity check.`

### 4. Check the public key

> **VALID alone is about the key in the file**
>
> Without `--expect-key`, `evidence verify` checks the signature against the `publicKey` written inside the attestation. Anyone can create a key and sign a report, so a `VALID` result means "signed by the holder of this public key". It becomes evidence about a person or company only once that key is the one they published.

Save the signer's published public key in a file, either as the `0x` hex printed by `keygen` or as an ed25519 PEM public key, and pass it with `--expect-key`. An attestation signed by any other key is then `INVALID`, even when its signature and report are fine. A file that does not hold a valid ed25519 key stops the command with exit code 2.

**settlens evidence verify --expect-key**

```text
$ settlens evidence verify attestation.json --report reports/settlens-4b9f875a.json \
    --expect-key signer.pub
VALID evidence attestation
Signature valid: true
Report matches:  true
Expected signer: true
Public key:      0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2
$ settlens evidence verify attestation.json --report reports/settlens-4b9f875a.json \
    --expect-key someone-else.pub
INVALID evidence attestation
Signature valid: true
Report matches:  true
Expected signer: false
Public key:      0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2
Reasons:
  - The attestation is signed by 0xfe6e01fca8a65ea84b985a68ce4955bb5ad6495331408ad62bbd239103e582e2, not by the expected key 0xbdeb61a7ac6a6d43ec8baa58efa395d3ccb2d2d1f943d8f940753b20ec2e6961.
```

The MCP `verify_attestation` tool does the same with its `expected_public_key` argument and returns `publicKeyMatches`.

## Attestation format

_Attestation fields_

| Field | Content |
| --- | --- |
| kind | Always settlens-evidence-attestation. |
| version | Attestation format version, 1. |
| canonicalization | Canonical JSON form that was signed: settlens-stable-json-v2. Attestations made before this field existed are verified with settlens-stable-json-v1. |
| reportId | reportId of the attested report. |
| productVersion | Settlens version that produced the report. |
| protocolVersion | x402 protocol version of the report. |
| subjects | Target URLs in the report, as written in it. |
| summary | The report's pass, fail, warn and skip counts. |
| reportDigest | The report's integrity.digest (SHA-256). This is what binds the attestation to one exact report. |
| attestedAt | ISO 8601 time from the signer's clock. |
| publicKey | Raw 32-byte ed25519 public key, hex with 0x. |
| signature | ed25519 signature over every other field, hex with 0x. |

The signed message is every field except `signature`, serialized with the canonical JSON named in `canonicalization` (`settlens-stable-json-v2`: keys sorted by UTF-16 code unit, no whitespace), the same form as the report digest. Because the name is signed, it cannot be changed without breaking the signature. The attestation therefore covers the summary and subjects as well as the digest: editing the pass count in the file invalidates it.

## What an attestation proves

- **It proves** that the holder of the private key for `publicKey` signed this exact report digest and summary, and that the report you hold is that report.
- **It does not prove** when the scan happened: `attestedAt` comes from the signer's clock, not from a trusted timestamping service.
- **It does not prove** that the assessed endpoint is secure. The report describes behavior observed during the scan, under the limits set out in the [methodology](https://settlens.com/docs/methodology). It is not a certification.

## Key management

- Keep the private key in a secret store, or on the machine that signs, never in the repository or in CI logs. A CI job that signs needs it as a secret file.
- Use one key per signing identity, for example one for your security team, and publish each public key once.
- To rotate, generate a new key and publish the new public key. Attestations made with the old key stay verifiable, because each one carries its public key.
- Settlens has no revocation list. If a key leaks, announce through the channel where you published it that attestations made with it after a given date must not be trusted.

## Questions

**Do I need Settlens to verify an attestation?**

The Settlens CLI and the MCP verify_attestation tool do it in one step. Any ed25519 library can check the signature too, provided it rebuilds the signed message exactly: the attestation without its signature field, serialized with Settlens' canonical JSON.

**Does Settlens hold my signing key?**

No. Keys are generated and used locally with node:crypto. Settlens never receives, stores or needs your private key.

**Can I attest a report that someone else produced?**

Yes. Attesting means that you vouch for that exact report. Verify it with verify-report first; an attestation of an edited report fails verification when the report is supplied.

Related: [report formats](https://settlens.com/docs/reports), the [Evidence product page](https://settlens.com/product/evidence), [why payment security reports need signed evidence](https://settlens.com/blog/signed-evidence-for-payment-assessments), and the offline verification tools of the [MCP server](https://settlens.com/docs/mcp).

## Sources

1. [RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)](https://www.rfc-editor.org/rfc/rfc8032.html) — IRTF
2. [FIPS 180-4: Secure Hash Standard (SHS)](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) — NIST
