Skip to content
Registry StackDocsDevelopment (unreleased)

Verify an assertion as a consumer

For the consumer or verifier

View as Markdown

If your team must stand behind an Evidence decision later, you replay it offline from what your application kept, then see a changed response refused. You continue the project from Request Evidence from your application; nothing needs to be running.

Outcome
An adult-status decision replayed at its recorded instant and a tampered copy refused.
Time
About 5 minutes
Level
Local verification with synthetic data
Prerequisites
The project and three files from Request Evidence from your applicationThe Python environment from that tutorial, activated again in a new terminal

In the project directory, the application wrote three files, and a replay needs nothing else:

Terminal window
ls adult-status.retained.json adult-status.response.jws adult-status.verified-at.txt
adult-status.response.jws
adult-status.retained.json
adult-status.verified-at.txt
  • adult-status.retained.json, the retained verification: the provider keys the client trusted and every expectation, fixed before the response existed. It holds no disclosed values.
  • adult-status.response.jws, the exact response bytes, still untrusted input.
  • adult-status.verified-at.txt, the decision instant in Unix seconds.

verify_retained judges a response against the current time. verify_retained_as_of asks whether the response was acceptable at the recorded instant, so a replay still holds after the assertion expires. Save this program as replay_adult_status.py:

replay_adult_status.py
import json
from pathlib import Path
from registry_client.evidence import VerificationError, verify_retained_as_of
context = Path("adult-status.retained.json").read_bytes()
response = Path("adult-status.response.jws").read_bytes()
decided_at = int(Path("adult-status.verified-at.txt").read_text())
replayed = verify_retained_as_of(context, response, decided_at)
value = replayed.evidence["supportedValues"][0]["value"]
print(f"REPLAYED adult={json.dumps(value)}")
document = json.loads(response)
payload = document["payload"]
document["payload"] = ("A" if payload[0] != "A" else "B") + payload[1:]
try:
verify_retained_as_of(context, json.dumps(document).encode(), decided_at)
except VerificationError as error:
print(f"REFUSED {error.code}")

The first call replays the decision. The second changes one character of the signed payload and keeps the keys, expectations, and instant. Run it:

Terminal window
python3 replay_adult_status.py
REPLAYED adult=true
REFUSED signature

The replay opens no connection and reads no current key set: the retained verification is its only trust input. error.code names the verifier’s reason; branch on it, never on the message.

In Node, verifyRetainedAsOf takes the instant in milliseconds, so the recorded seconds are scaled:

replay-adult-status.js
const { readFileSync } = require('node:fs')
const { evidence } = require('@registrystack/client')
const replayed = evidence.verifyRetainedAsOf(
readFileSync('adult-status.retained.json'),
readFileSync('adult-status.response.jws'),
Number(readFileSync('adult-status.verified-at.txt', 'utf8')) * 1000,
)
console.log(`REPLAYED adult=${replayed.evidence.supportedValues[0].value}`)

The replay uses only the three files, so a provider’s later key rotation or a change to your current trust configuration cannot make an unchanged replay fail. A refusal of a decision that once replayed means one of two things:

  • The retained files changed or are corrupt. A VerificationError names which check failed in error.code; a context that no longer parses is refused as invalid configuration before any check runs. Restore them from wherever your application keeps its records.
  • The client you replay with does not accept the retained format, for example after an upgrade. Replay with a client version that reads it, and report the incompatibility; it is not a trust decision.

You are done with the local session. Registry Evidence is already stopped, so remove its state, including the audit history audit show reads. Keep the project directory: building and deploying the project starts what it needs itself.

Terminal window
rstack evidence dev clean .
Removed stopped local Evidence state