REDACT / Protocol
Protocol specification, draft 0.2

How a page becomes a proof.

REDACT is a four-stage pipeline: a notarized TLS session, a commitment to the response, selective disclosure of one field, and a zero-knowledge proof of a predicate over that field. This page describes each stage, the trust assumptions, and where the design deliberately gives up generality for safety.

Overview

A conventional oracle asks a trusted party to observe the world and report it. REDACT inverts this. The user already observes the world every time they load an authenticated page. The only thing missing is a way to prove what they saw without handing over the session that let them see it.

TLS already provides integrity and origin authentication for the response. What it does not provide is transferability: the session key that proves the response came from the server is the same key that would let the user forge one. REDACT resolves this by splitting the session key between the client and a notary using multi-party computation, so that neither can forge alone, and then proving statements about the transcript in zero knowledge.

Input

Authenticated HTTPS session

Any origin. No cooperation from the origin.

Trust

One notary

Cannot see plaintext. Cannot forge alone. Replaceable.

Output

Groth16 proof

Roughly 300 bytes. Fixed circuit. Chain-agnostic.

Disclosure

One predicate

Origin, predicate, timestamp. Nothing else.

1. Session: MPC-TLS

The client initiates a TLS 1.3 handshake with the origin. Key derivation for the session is performed jointly with a notary using two-party computation. The client holds one share of the encryption key and the notary holds the other. The origin sees an ordinary TLS client.

Because the client never holds the full session key, it cannot produce a ciphertext that would decrypt to an arbitrary plaintext under that key. Because the notary never sees the plaintext, it learns nothing about the request, the response, or the user's credentials.

sessionpseudocode
client_share, notary_share = mpc_derive(ecdhe_secret)
request  = encrypt(client_share, notary_share, GET /api/v1/users/self)
response = recv()          // ciphertext, held by client

Latency overhead of the MPC is currently 300 to 900 ms depending on response size. The origin's own rate limits and anti-bot controls apply unchanged, since the request is indistinguishable from a normal browser request.

2. Commitment

After the session closes, the notary signs a commitment to the full response ciphertext together with the session metadata: origin hostname, server certificate fingerprint, and a timestamp. The notary signs the commitment blind. It attests that this ciphertext was produced under the jointly derived key, which is equivalent to attesting that the origin sent it.

commitmentstructure
commitment = H(ciphertext || origin || cert_fingerprint || ts)
notary_sig = sign(notary_key, commitment)

The commitment is the anchor for everything downstream. A proof is only meaningful relative to a commitment, and a commitment is only meaningful relative to a notary key that verifiers recognize.

3. Selective disclosure

The client decrypts the response locally (it now has both key shares, since the session is over and the notary releases its share after signing). The client then identifies exactly one field by a JSON path, or for HTML responses, by a CSS selector resolved against a canonical DOM serialization.

Only the byte range of that field enters the proof as a private witness. Everything else in the response is committed to but never opened. This is what makes the disclosure boundary structural rather than a matter of policy: the circuit has no input for the rest of the response.

Why one field. Multi-field disclosure is technically straightforward and deliberately unsupported in v0.2. Every additional disclosed field increases the fingerprinting surface of the credential. A single predicate over a single field is the smallest useful unit of disclosure, and the protocol defaults to it.

4. Predicate proof

The client generates a Groth16 proof over a fixed circuit. The public inputs are the commitment, the origin hostname, the field path hash, the predicate, and the holder address. The private witnesses are the session key shares, the plaintext byte range of the field, and the Merkle path from that range to the commitment.

The circuit asserts three things: that the private range decrypts correctly under the committed key, that the range sits at the declared path in the response, and that the parsed value satisfies the predicate.

circuitpublic inputs
public:  commitment, origin_hash, path_hash, predicate, holder
private: key_shares, field_bytes, merkle_path

assert decrypt(key_shares, ciphertext[range]) == field_bytes
assert merkle_verify(field_bytes, merkle_path, commitment)
assert eval(predicate, parse(field_bytes)) == true

Supported predicates in v0.2: >=, <=, = over integers and fixed-point decimals, and = over hashed strings. Range proofs and set membership are scheduled for v0.3.

Proving time on a 2024 laptop is 1.5 to 4 seconds. On a recent phone, 6 to 12 seconds. Proof size is constant at roughly 300 bytes regardless of response size.

5. Credential

The proof, the public inputs, and the notary signature are submitted to the REDACT registry contract on the target chain. The contract verifies the proof, checks the notary key against its allowlist, and mints a non-transferable credential to the holder address. The credential stores only the public inputs and a nullifier derived from the commitment, which prevents the same session from producing more than one credential.

Verifiers can read the credential directly or verify the proof offchain against the registry's public inputs. See Verify for both paths.

Trust assumptions

  • Notary honesty. A notary colluding with a client can produce a valid commitment to a fabricated response. Mitigations: verifiers choose which notary keys they accept, multiple notaries can be required per proof, and notaries can run inside attested TEEs. See threat model.
  • Origin honesty. The proof attests to what the origin sent, not to whether it is true. A platform that lies about follower counts produces a valid proof of a lie.
  • Circuit correctness. A bug in the circuit could allow false proofs. The circuit is small, fixed, and audited. See audits.
  • Client integrity. Malware on the client can steal the session it already has access to. REDACT does not increase this exposure, but it does not reduce it either.

Known limits

  • Response size is capped at 64 KB in v0.2. Larger pages must be fetched via a narrower endpoint.
  • Origins that pin client certificates or use non-standard TLS stacks may not be supported.
  • Field paths are pinned per source version. When an origin changes its response shape, the source definition must be updated and re-pinned. See adding a source.
  • Proofs carry a timestamp, not a liveness guarantee. Verifiers must enforce freshness themselves.
Compose a proof Read the threat model Verifier documentation