Signed Bytes All chapters

Chapter 01 · the instrument

An attestation is a receipt. Only part of it is signed.

Paste a Primus zkTLS attestation. This page recomputes the digest byte for byte, the way PrimusZKTLS.sol and the JS SDK do, recovers the signer from the raw hash, and lays the packed bytes out as a tape. Everything on the tape is protected by the signature. Everything beside it can be rewritten by whoever hands you the JSON. Nothing leaves your browser.

01

The tape

abi.encodePacked, in signing order

Seven fields, concatenated with no padding and no separators, then hashed once with keccak256. Segment widths are drawn to real byte length. Click a segment to read the bytes that went in.

keccak256 →
fixed width hash of a sub-struct variable-length string not covered by the signature

Not on the tape

attestors and signatures are fields of the attestation struct, but they are not inputs to encodeAttestation. The on-chain verifier does not care: it ignores the claimed attestor list and checks the recovered address against its own registry. An off-chain verifier that reads attestation.attestors[0].attestorAddr and treats it as the signer is reading an unsigned field.

attestors[].attestorAddr attestors[].url signatures[]
02

Readout

nine checks, three layers

A valid signature answers one question: did the attestor sign this exact tape. The remaining questions are the integrator's, and the base contract does not answer them for you.

On-chain verifyAttestation Nothing to inspect yet.
Your integration Nothing to inspect yet.
Submission log Nothing recorded. Accept one, then submit it again.
03

Tamper bench

edit a field, watch the digest

Change anything with a solid tick and the digest moves, so the signature stops recovering to the attestor. Change anything with a dashed tick and the digest does not move at all.

Attestation fields

Four things an attacker tries

What you expect

If you never check this, an attestation issued to someone else verifies just fine against your contract.
Matched against the signed request URL as a substring.
Matched against every parsePath in reponseResolve.
The contract never reads the clock. Whatever window you want, you enforce.
04

Payload

paste the object your SDK returned

The full attestation object from startAttestation, or the JSON you post to your backend. It is parsed in the page and never sent anywhere. No attestation handy? Generate one in the Primus zkTLS Playground and paste it here. The playground labels its runs a simulation, but the attestations come back signed by the production attestor, so they verify here exactly like any other.

attestation json Open the Playground
05

How this reads an attestation

The digest

encodeRequest hashes url, header, method and body. encodeResponse hashes every keyName, parseType and parsePath in order. Those two hashes are packed together with recipient, data, attConditions, timestamp and additionParams, and the whole thing is hashed once more.

The signature

The attestor signs that digest directly, with no \x19Ethereum Signed Message prefix, so recovery runs on the raw 32 bytes. A 65-byte signature with v of 27 or 28 recovers exactly one address, and the verifier accepts it only if that address sits in the contract's attestor registry.

What is left to you

Recipient binding, endpoint pinning, response-path pinning, freshness and replay are all integration concerns. Each one is a line of code you either wrote or did not. This page shows you which.

Check yourself

four questions

Everything below can be settled with the bench above. If one of them surprises you, go back and press the button.

Next in the series

Chapter 02 takes the same attestation and asks a narrower question: of everything inside that TLS session, what actually left it, and in what form.

All ten chapters