Introduction

Data Provenance without Compromising Privacy, That is Why!

The Internet currently lacks effective, privacy-preserving Data Provenance. TLS, also known as the "s" in "https" πŸ” to the general public, ensures that data can be securely communicated between a server and a user. But how can this user credibly share this data with another user or server without compromising security, privacy, and control?

Enter TLSNotary: a protocol enabling users to export data securely from any website. Using Zero Knowledge Proof (ZKP) technology, this data can be selectively shared with others in a cryptographically verifiable manner.

TLSNotary makes data truly portable and allows a user, the Prover, to share it with another party, the Verifier, as they see fit.

How Does the TLSNotary Protocol Work?

The TLSNotary protocol consists of 3 steps:

  1. The Prover requests data from a Server over TLS while cooperating with the Verifier in secure and privacy-preserving multi-party computation (MPC).
  2. The Prover selectively discloses the data to the Verifier.
  3. The Verifier verifies the data.

β‘  Multi-party TLS Request

TLSNotary works by adding a third party, a Verifier, to the usual TLS connection between the Prover and a Server. This Verifier is not "a man in the middle". Instead, the Verifier participates in a secure multi-party computation (MPC) to jointly operate the TLS connection without seeing the data in plain text. By participating in the MPC, the Verifier can validate the authenticity of the data the Prover received from the Server.

The TLSNotary protocol is transparent to the Server. From the Server's perspective, the Prover's connection is a standard TLS connection.

β‘‘ Selective Disclosure

The TLSNotary protocol enables the Prover to selectively prove the authenticity of arbitrary parts of the data to a Verifier. In this selective disclosure phase, the Prover can redact sensitive information from the data prior to sharing it with the Verifier.

This capability can be paired with Zero-Knowledge Proofs to prove properties of the redacted data without revealing the data itself.

β‘’ Data Verification

The Verifier now validates the proof received from the Prover. The data origin can be verified by inspecting the Server certificate through trusted certificate authorities (CAs). The Verifier can now make assertions about the non-redacted content of the transcript.

TLS verification with a general-purpose Notary

Since the validation of the TLS traffic neither reveals anything about the plaintext of the TLS session nor about the Server, it is possible to outsource the MPC-TLS verification β‘  to a general-purpose TLS verifier, which we term a Notary. This Notary can sign (aka notarize) β‘‘ the data, making it portable. The Prover can then take this signed data and selectively disclose β‘’ sections to an application-specific Verifier, who then verifies the data β‘£.

In this setup, the Notary cryptographically signs commitments to the data and the server's identity. The Prover can store this signed data, redact it, and share it with any Verifier as they see fit, making the signed data both reusable and portable.

Verifiers will only accept the signed data if they trust the Notary. A data Verifier can also require signed data from multiple Notaries to rule out collusion between the Prover and a Notary.

What Can TLSNotary Do?

TLSNotary can be used for various purposes. For example, you can use TLSNotary to prove that:

  • you have access to an account on a web platform
  • a website showed specific content on a certain date
  • you have private information about yourself (address, birth date, health, etc.)
  • you have received a money transfer using your online banking account without revealing your login credentials or sensitive financial information
  • you received a private message from someone
  • you purchased an item online
  • you were blocked from using an app
  • you earned professional certificates

While TLSNotary can notarize publicly available data, it does not solve the "oracle problem". For this use case, existing oracle solutions are more suitable.

What TLS version does TLSNotary support?

TLSNotary currently supports TLS 1.2. TLS 1.3 support will be added in 2024.

Who is behind TLSNotary?

TLSNotary is developed by the Privacy and Scaling Exploration (PSE) research lab of the Ethereum Foundation. The PSE team is committed to conceptualizing and testing use cases for cryptographic primitives.

TLSNotary is not a new project; in fact, it has been around for more than a decade.

In 2022, TLSNotary was rebuilt from the ground up in Rust incorporating state-of-the-art cryptographic protocols. This renewed version of the TLSNotary protocol offers enhanced security, privacy, and performance.

Older versions of TLSNotary, including PageSigner, have been archived due to a security vulnerability.

Motivation

The decentralized internet demands privacy-respecting data provenance!

Data provenance ensures internet data is authentic. It allows verification of the data's origin and ensures the data hasn't been fabricated or tampered with.

Data provenance will make data truly portable, empowering users to share it with others as they see fit.

Non-repudiation: TLS is not enough

Transport Layer Security (TLS) plays a crucial role in digital security. TLS protects communication against eavesdropping and tampering. It ensures that the data received by a user ("Alice") indeed originated from the Server and was not changed. The Server's identity is verified by Alice through trusted Certificate Authorities (CAs). Data integrity is maintained by transmitting a cryptographic hash (called Message Authentication Code or MAC in TLS) alongside the data, which safeguards against deliberate alterations.

However, this hash does not provide non-repudiation, meaning it cannot serve as evidence for the authenticity and integrity of the data to Bob (e.g., a service or an app). Because it is a keyed hash and TLS requires that the key is known to Alice, she could potentially modify the data and compute a corresponding hash after the TLS session is finished.

Achieving non-repudiation requires digital signatures implemented with asymmetric, public-key cryptography.

While the concept seems straightforward, enabling servers to sign data is not a part of the TLS protocol. Even if all data were securely signed, naively sharing all data with others could expose too much information, compromising Alice's privacy. Privacy is a vital social good that must be protected.

Status Quo: delegate access

Currently, when Alice wants to share data from a Server with another party, OAuth can be used to facilitate this if the application supports it. In this way, the other party receives the data directly from the Server, ensuring authentic and unchanged data. However, applications often do not provide fine-grained control over which data to share, leading to the other party gaining access to more information than strictly necessary.

Another drawback of this solution is that the Server is aware of the access delegation, enabling it to monitor and censor the other user’s requests.

It's worth noting that in many instances, OAuth is not even presented as an option. This is because a lot of servers lack the incentive to provide third-party access to the data.

TLSNotary: data provenance and privacy with secure multi-party computation

TLSNotary operates by executing the TLS communication using multi-party computation (MPC). MPC allows Alice and Bob to jointly manage the TLS connection. With TLSNotary, Alice can selectively prove the authenticity of arbitrary portions of the data to Bob. Since Bob participated in the MPC-TLS communication, he is guaranteed that the data is authentic.

The TLSNotary protocol is transparent to the Server. From the Server's perspective, the TLS connection appears just like any other connection, meaning no modifications to the TLS protocol are necessary.

Make your data portable with TLSNotary!

TLSNotary is a solution designed to prove the authenticity of data while preserving user privacy. It unlocks a variety of new use cases. So, if you're looking for a way to make your data portable without compromising on privacy, TLSNotary is developed for you!

Dive into the protocol and integrate it into your applications. We eagerly await your feedback on Discord.

FAQ

Doesn't TLS allow a third party to verify data authenticity?

No, it does not. TLS is designed to guarantee the authenticity of data only to the participants of the TLS connection. TLS does not have a mechanism to enable the server to "sign" the data.

The TLSNotary protocol overcomes this limitation by making the third-party Verifier a participant in the TLS connection.

How exactly does a Verifier participate in the TLS connection?

The Verifier collaborates with the Prover using secure multi-party computation (MPC). There is no requirement for the Verifier to monitor or to access the Prover's TLS connection. The Prover is the one who communicates with the server.

What are the trust assumptions of the TLSNotary protocol?

The protocol does not have trust assumptions. In particular, it does not rely on secure hardware or on the untamperability of the communication channel.

The protocol does not rely on participants to act honestly. Specifically, it guarantees that, on the one hand, a malicious Prover will not be able to convince the Verifier of the authenticity of false data, and, on the other hand, that a malicious Verifier will not be able to learn the private data of the Prover.

What is the role of a Notary?

In some scenarios where the Verifier is unable to participate in a TLS connection, they may choose to delegate the verification of the online phase of the protocol to an entity called the Notary.

Just like the Verifier would (see FAQ above), the Notary collaborates with the Prover using MPC to enable the Prover to communicate with the server. At the end of the online phase, the Notary produces an attestation trusted by the Verifier. Then, in the offline phase, the Verifier is able to ascertain data authenticity based on the attestation.

Is the Notary an essential part of the TLSNotary protocol?

No, it is not essential. The Notary is an optional role which we introduced in the tlsn library as a convenience mode for Verifiers who choose not to participate in the TLS connection themselves.

For historical reasons, we continue to refer to the protocol between the Prover and the Verifier as the "TLSNotary" protocol, even though the Verifier may choose not to use a Notary.

Which TLS versions are supported?

We support TLS 1.2, which is an almost-universally deployed version of TLS on the Internet. There are no immediate plans to support TLS 1.3. Once the web starts to transition away from TLS 1.2, we will consider adding support for TLS 1.3 or newer.

What is the overhead of using the TLSNotary protocol?

Due to the nature of the underlying MPC, the protocol is bandwidth-bound. We are in the process of implementing more efficient MPC protocols designed to decrease the total data transfer.

With the upcoming protocol upgrade planned for 2025, we expect the Prover's upload data overhead to be:

~25MB (a fixed cost per one TLSNotary session) + ~10 MB per every 1KB of outgoing data + ~40KB per every 1 KB of incoming data.

In a concrete scenario of sending a 1KB HTTP request followed by a 100KB response, the Prover's overhead will be:

25 + 10 + 4 = ~39 MB of upload data.

Does TLSNotary use a proxy?

A proxy is required only for the browser extension because browsers do not allow extensions to open TCP connections. Instead, our extension opens a websocket connection to a proxy (local or remote) which opens a TCP connection with the server. Our custom TLS client is then attached to this connection and the proxy only sees encrypted data.

PSE hosts a WebSocket proxy that you can use for development and experimentation. Note that this proxy supports only a limited whitelist of domains. For other domains, you can easily run your own local WebSocket by following these steps.

Why does my session time out?

If you are experiencing slow performance or server timeouts, make sure you are building with the --release profile. Debug builds are significantly slower due to extra checks. Use:

cargo run --release

How to run TLSNotary with extra logging?

To get deeper insights into what TLSNotary is doing, you can enable extra logging with RUST_LOG=debug or RUST_LOG=trace. This will generate a lot of output, as it logs extensive network activity. It’s recommended to filter logs for better readability. The recommended configuration is:

RUST_LOG=trace,yamux=info,uid_mux=info cargo run  --release

In the Browser Extension you change the logging level via Options > Advanced > Logging Level

How do I troubleshoot connection issues?

If a TLSNotary request fails, first ensure that the request works independently of TLSNotary by testing it with tools like curl, Postman, or another HTTP client. This helps rule out any server or network issues unrelated to TLSNotary.

Next, confirm that your request includes the necessary headers:

  • Accept-Encoding: identity to avoid compressed responses.
  • Connection: close to ensure the server closes the connection after the response.

If the issue persists, enable extra logging with RUST_LOG=debug or RUST_LOG=trace for deeper insights into what TLSNotary is doing.

If you are connecting through a WebSocket proxy (e.g., in the browser extension), double-check that the WebSocket proxy connects to the intended domain. Note that PSE's public WebSocket proxy only supports a limited whitelist. If you use a local proxy, make sure the domain is correct.

Does TLSNotary Solve the Oracle Problem?

No, the TLSNotary protocol does not solve the "Oracle Problem." The Oracle Problem refers to the challenge of ensuring that off-chain data used in blockchain smart contracts is trustworthy and tamper-proof. While TLSNotary allows a Prover to cryptographically authenticate TLS data to a designated Verifier, trust is still required in the designated Verifier when it attests to the verified data on-chain. Therefore, this is not a trustless, decentralized solution to the Oracle Problem.

TLSNotary can be used to bring data on-chain, but when the stakes are high, it is recommended to combine TLSNotary with a dedicated oracle protocol to mitigate these risks. Multiple projects are currently exploring the best solutions.

What is a presentation in TLSNotary?

In TLSNotary, a presentation refers to data shared by the Prover to selectively reveal specific parts of the TLS data committed to earlier during the attestation phase. By using these earlier commitments, the Prover can choose to disclose only particular segments of the TLS data while keeping other parts hidden or redacted. This enables a flexible and controlled way to share proofs, ensuring that sensitive information remains private.

The term β€œpresentation” is inspired by similar terminology in the W3C Verifiable Credentials standard.

Quick Start

This quick start will help you get started with TLSNotary, both in native Rust and in the browser.

  1. Most Basic Example: Proving and Verifying Public Data (Rust)
  2. Proving and Verifying a Private Discord DM (Rust)
  3. Proving and Verifying data in a React/Typescript app
  4. Proving and Verifying ownership of a Twitter account (Browser)

Objectives of this quick start:

  • Gain a better understanding of what you can do with TLSNotary
  • Learn the basics of how to prove and verify data using TLSNotary

Let's start!

Rust Quick Start

This Quick Start will show you how to use TLSNotary in a native Rust application.

Requirements

Before we start, make sure you have cloned the tlsn repository and have a recent version of Rust installed.

Clone the TLSNotary Repository

Clone the tlsn repository (defaults to the main branch, which points to the latest release):

git clone https://github.com/tlsnotary/tlsn.git

Next open the tlsn folder in your favorite IDE.

Install Rust

If you don't have Rust installed yet, you can install it using rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

To configure your current shell, run:

source "$HOME/.cargo/env"

Simple Example: Notarizing Public Data from example.com

This example demonstrates the simplest possible use case for TLSNotary:

  1. Fetch https://example.com/ and acquire an attestation of its content.
  2. Create a verifiable presentation using the attestation, while redacting the value of a header.
  3. Verify the presentation.

1. Notarize https://example.com/

Run the prove binary:

cd crates/examples/attestation
cargo run --release --example attestation_prove

If the notarization was successful, you should see this output in the console:

Starting an MPC TLS connection with the server
Got a response from the server
Notarization completed successfully!
The attestation has been written to `example.attestation.tlsn` and the corresponding secrets to `example.secrets.tlsn`.

If you want to see more details, you can run the prover with extra logging:

RUST_LOG=DEBUG,uid_mux=INFO,yamux=INFO cargo run --release --example attestation_prove

⚠️ In this simple example the Notary server is automatically started in the background. Note that this is for demonstration purposes only. In a real world example, the notary should be run by a trusted party. Consult the Notary Server Docs for more details on how to run a notary server.

2. Build a verifiable presentation

This will build a verifiable presentation with the User-Agent header redacted from the request. This presentation can be shared with any verifier you wish to present the data to.

Run the present binary.

cargo run --release --example attestation_present

If successful, you should see this output in the console:

Presentation built successfully!
The presentation has been written to `example.presentation.tlsn`.

3. Verify the presentation

This will read the presentation from the previous step, verify it, and print the disclosed data to console.

Run the verify binary.

cargo run --release --example attestation_verify

If successful, you should see this output in the console:

Verifying presentation with {key algorithm} key: { hex encoded key }

**Ask yourself, do you trust this key?**

-------------------------------------------------------------------
Successfully verified that the data below came from a session with example.com at 2024-10-03 03:01:40 UTC.
Note that the data which the Prover chose not to disclose are shown as X.

Data sent:
...

⚠️ Notice that the presentation comes with a "verifying key". This is the key the Notary used when issuing the attestation that the presentation was built from. If you trust the Notary, or more specifically the verifying key, then you can trust that the presented data is authentic.

Notarizing Private Information: Discord Message

Next, we will use TLSNotary to generate a proof of private information: a private Discord DM.

We will also use an explicit (locally hosted) notary server this time.

1. Start a Local Notary Server

The notary server used in this example is more functional compared to the (implicit) simple notary service used in the example above. This notary server should actually be run by the Verifier or a neutral party. To make things simple, we run everything on the same machine.

  1. Edit the notary server config file (crates/notary/server/config/config.yaml) to turn off TLS so that self-signed certificates can be avoided (⚠️ this is only for local development purposes β€” TLS must be used in production).
     tls:
         enabled: false
         ...
    
  2. Run the notary server:
    cd crates/notary/server
    cargo run --release
    

The notary server will now be running in the background waiting for connections.

Keep it running and open a new terminal.

2. Get Authorization Token and Channel ID

Before we can notarize a Discord message, we need some parameters in a .env file.

In the tlsn/examples/discord folder, copy the .env.example file and name it .env.

In this .env, we will input the USER_AGENT, AUTHORIZATION token, and CHANNEL_ID.

NameExampleLocation
USER_AGENT"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0"Look for User-Agent in request headers
AUTHORIZATION"MTE1NDe1Otg4N6NxNjczOTM2OA.GYbUBf.aDtcMUKDOmg6C2kxxFtlFSN1pgdMMBtpHgBBEs"Look for Authorization in request headers
CHANNEL_ID"1154750485639745567"URL

You can obtain these parameters by opening Discord in your browser and accessing the message history you want to notarize.

NOTE: ⚠️ Please note that notarizing only works for short transcripts at the moment, so choose a contact with a short history.

Next, open the Developer Tools, go to the Network tab, and refresh the page. Then, click on Search and type /api to filter results to Discord API requests. From there, you can copy the needed information into your .env as indicated above.

You can find the CHANNEL_ID directly in the URL:

https://discord.com/channels/@me/{CHANNEL_ID)

Discord Authentication Token

3. Notarize

In this tlsn/examples/discord folder, run the following command:

RUST_LOG=DEBUG,uid_mux=INFO,yamux=INFO cargo run --release --example discord_dm

If everything goes well, you should see output similar to the following:

...
2024-06-26T08:49:47.017439Z DEBUG connect:tls_connection: tls_client_async: handshake complete
2024-06-26T08:49:48.676459Z DEBUG connect:tls_connection: tls_client_async: server closed connection
2024-06-26T08:49:48.676481Z DEBUG connect:commit: tls_mpc::leader: committing to transcript
2024-06-26T08:49:48.676503Z DEBUG connect:tls_connection: tls_client_async: client shutdown
2024-06-26T08:49:48.676466Z DEBUG discord_dm: Sent request
2024-06-26T08:49:48.676550Z DEBUG discord_dm: Request OK
2024-06-26T08:49:48.676598Z DEBUG connect:close_connection: tls_mpc::leader: closing connection
2024-06-26T08:49:48.676613Z DEBUG connect: tls_mpc::leader: leader actor stopped
2024-06-26T08:49:48.676618Z DEBUG discord_dm: [
  {
    "attachments": [],
    ...
    "channel_id": "1154750485639745567",
    ...
  }
]
2024-06-26T08:49:48.678621Z DEBUG finalize: tlsn_prover::tls::notarize: starting finalization
2024-06-26T08:49:48.680839Z DEBUG finalize: tlsn_prover::tls::notarize: received OT secret
2024-06-26T08:49:50.004432Z  INFO finalize:poll{role=Client}:handle_shutdown: uid_mux::yamux: mux connection closed
2024-06-26T08:49:50.004448Z  INFO finalize:poll{role=Client}: uid_mux::yamux: connection complete
2024-06-26T08:49:50.004583Z DEBUG discord_dm: Notarization complete!

🍾 Great job! You have successfully used TLSNotary in Rust.

TLSNotary in React/Typescript with tlsn-js

In this Quick Start you will learn how to use TLSNotary in React/Typescript with tlsn-js NPM module in the browser.

This Quick Start uses the react/typescript demo app in tlsn-js. The directory contains a webpack configuration file that allows you to quickly bootstrap a webpack app using tlsn-js.

tlsn-js in a React/Typescript app

In this demo, we will request JSON data from the Star Wars API at https://swapi.dev. We will use tlsn-js to notarize the TLS request with TLSNotary and store the result in a proof. Then, we will use tlsn-js again to verify this proof.

NOTE: ℹ️ This demo uses TLSNotary to notarize public data to simplify the Quick Start for everyone. For real-world applications, TLSNotary is particularly valuable for notarizing private and sensitive data.

  1. Clone the repository
    git clone https://github.com/tlsnotary/tlsn-js    
    
  2. Navigate to the demo directory:
    cd tlsn-js/demo/react-ts-webpack
    
  3. Checkout the version of this Quick Start:
    git checkout v0.1.0-alpha.7
    
  4. If you want to use a local TLSNotary server: Run a local notary server and websocket proxy, otherwise:
    1. Open app.tsx in your favorite editor.
    2. Replace notaryUrl: 'http://localhost:7047', with:
         notaryUrl: 'https://notary.pse.dev/v0.1.0-alpha.7',
      
      This makes this webpage use the PSE notary server to notarize the API request. Feel free to use different or local notary; a local server will be faster because it removes the bandwidth constraints between the user and the notary.
    3. Replace websocketProxyUrl: 'ws://localhost:55688', with:
          websocketProxyUrl: 'wss://notary.pse.dev/proxy?token=swapi.dev',
      
      Because a web browser doesn't have the ability to make TCP connection, we need to use a websocket proxy server. This uses a proxy hosted by PSE. Feel free to use different or local notary proxy.
    4. In package.json: check the version number:
          "tlsn-js": "v0.1.0-alpha.7"
      
  5. Install dependencies
    npm i
    
  6. Start Webpack Dev Server:
    npm run dev
    
  7. Open http://localhost:8080 in your browser
  8. Click the Start demo button
  9. Open Developer Tools and monitor the console logs

Run a local notary server and websocket proxy (Optional)

The instructions above, use the PSE hosted notary server and websocket proxy. This is easier for this Quick Start because it requires less setup. If you develop your own applications with tlsn-js, development will be easier with locally hosted services. This section explains how.

Websocket Proxy

Since a web browser doesn't have the ability to make TCP connection, we need to use a websocket proxy server.

  1. Install wstcp:
cargo install wstcp
  1. Run a websocket proxy for https://swapi.dev:
wstcp --bind-addr 127.0.0.1:55688 swapi.dev:443

Note the swapi.dev:443 argument on the last line, this is the server we will use in this quick start.

Run a Local Notary Server

For this demo, we also need to run a local notary server.

  1. Clone the TLSNotary repository (defaults to the main branch, which points to the latest release):
    git clone https://github.com/tlsnotary/tlsn.git
    
  2. Edit the notary server config file (crates/notary/server/config/config.yaml) to turn off TLS so that self-signed certificates can be avoided (⚠️ this is only for local development purposes β€” TLS must be used in production).
    tls:
       enabled: false
    
  3. Run the notary server:
    cd crates/notary/server
    cargo run --release
    

The notary server will now be running in the background waiting for connections.

TLSNotary Browser Extension

In this Quick Start we will prove ownership of a Twitter account with TLSNotary's browser extension. First we need to install and configure a websocket proxy and a notary server.

Install Browser Extension (Chrome/Brave)

The easiest way to install the TLSN browser extension is to use Chrome Web Store.

Alternatively, you can install it manually:

  1. Download the browser extension from https://github.com/tlsnotary/tlsn-extension/releases/download/0.1.0.700/tlsn-extension-0.1.0.700.zip
  2. Unzip
    ⚠️ This is a flat zip file, so be careful if you unzip from the command line, this zip file contains many file at the top level
  3. Open Manage Extensions: chrome://extensions/
  4. Enable Developer mode
  5. Click the Load unpacked button
  6. Select the unzipped folder

(Optional:) Pin the extension, so that it is easier to find in the next steps:

Websocket Proxy

Since a web browser doesn't have the ability to make TCP connection, we need to use a websocket proxy server. You can either run one yourself, or use a TLSNotary hosted proxy.

To use the TLSnotary hosted proxy:

  1. Open the extension
  2. Click Options
  3. Enter wss://notary.pse.dev/proxy as proxy API
  4. Click Save

To run your own websocket proxy locally, run:

  1. Install wstcp:
cargo install wstcp
  1. Run a websocket proxy for https://swapi.dev:
wstcp --bind-addr 127.0.0.1:55688 swapi.dev:443

Note the api.x.com:443 argument on the last line.

Next use ws://localhost:55688 as proxy API in Step 3 above.

Notary Server

To create a TLSNotary proof, the browser extension needs a TLSNotary notary server. In a real world scenario, this server should be run by a neutral party, or by the verifier of the proofs. In this quick start, you can either run the server yourself or use the test server from the TLSNotary team. Notarizing TLS with Multi Party Computation involves a lot of communication between the extension and notary server, so running a local server is the fastest option.

To use the TLSNotary team notary server:

  1. Open the extension
  2. Click Options
  3. Update Notary API to: https://notary.pse.dev/v0.1.0-alpha.7
  4. Click Save
  5. Skip the next section and continue with the notarization step

If you plan to run a local notary server:

  1. Open the extension
  2. Click Options
  3. Update Notary API to: http://localhost:7047
  4. Click Save
  5. Run a local notary server (see below)

Run a Local Notary Server

  1. Clone the TLSNotary repository (defaults to the main branch, which points to the latest release):
       git clone https://github.com/tlsnotary/tlsn.git
    
  2. Edit the notary server config file (crates/notary/server/config/config.yaml) to turn off TLS so that the browser extension can connect to the local notary server without requiring extra steps to accept self-signed certificates in the browser (⚠️ this is only for local development purposes β€” TLS must be used in production).
     tls:
         enabled: false
         ...
    
  3. Run the notary server:
    cd crates/notary/server
    cargo run --release
    

The notary server will now be running in the background waiting for connections.

Notarize Twitter Account Access

  1. Open Twitter https://twitter.com and login if you haven't yet.
  2. Open the extension, you should see requests being recorded:
  3. Click on Requests
  4. Enter the text setting in search box
  5. Select the GET xmlhttprequest /1.1/account/settings.json request, and then click on Notarize
  6. Select any headers that you would like to reveal.
  7. Highlight the text that you want to make public to hide everything else.
  • Click Notarize, you should see your notarization being processed:

If you use the hosted notary server, notarization will take multiple seconds. You can track progress by opening the offscreen console:

Verify

When the notarization is ready, you can click View Proof. If you did close the UI, you can find the proof by clicking History and View Proof.

Troubleshooting

  • Requests(0): no requests in the Browser extension ➑ restart the TLSN browser extension in chrome://extensions/ and reload the Twitter page.
  • Are you using a local notary server? ➑ Check notary server's console log.

Run a Notary Server

This guide shows you how to run a notary server in an Ubuntu server instance.

Configure Server Setting

All the following settings can be configured in the config file.

  1. Before running a notary server you need the following files. ⚠️ The default dummy fixtures are for testing only and should never be used in production.

    FilePurposeFile TypeCompulsory to changeSample Command
    TLS private keyThe private key used for the notary server's TLS certificate to establish TLS connections with proversTLS private key in PEM formatYes unless TLS is turned off<Generated when creating CSR for your Certificate Authority, e.g. using Certbot>
    TLS certificateThe notary server's TLS certificate to establish TLS connections with proversTLS certificate in PEM formatYes unless TLS is turned off<Obtained from your Certificate Authority, e.g. Let's Encrypt>
    Notary signature private keyThe private key used for the notary server's signature on the generated transcript of the TLS sessions with proversA K256 elliptic curve private key in PKCS#8 PEM formatYesopenssl genpkey -algorithm EC -out eckey.pem -pkeyopt ec_paramgen_curve:secp256k1 -pkeyopt ec_param_enc:named_curve
    Notary signature public keyThe public key used for the notary server's signature on the generated transcript of the TLS sessions with proversA matching public key in PEM formatYesopenssl ec -in eckey.pem -conv_form compressed -pubout -out eckey.pub
  2. Expose the notary server port (specified in the config file) on your server networking setting

  3. Optionally one can turn on authorization, or turn off TLS if TLS is handled by an external setup, e.g. reverse proxy, cloud setup

Using Cargo

  1. Install required system dependencies
sudo apt-get update && sudo apt-get upgrade
sudo apt-get install libclang-dev pkg-config build-essential libssl-dev
  1. Install rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
  1. Download notary server source code
 mkdir ~/src; cd ~/src
 git clone https://github.com/tlsnotary/tlsn.git
  1. Switch to your desired released version, or stay in the main branch to use the latest version (⚠️ only prover of the same version is supported for now)
git checkout tags/<version>
  1. To configure the server setting, please refer to the Using Cargo section in the repo's readme
  2. Run the server
cd crates/notary/server
cargo run --release

Using Docker

  1. Install docker following your preferred method here
  2. To configure the server setting, please refer to the Using Docker section in the repo's readme
  3. Run the notary server docker image of your desired version (⚠️ only prover of the same version is supported for now)
docker run --init -p 127.0.0.1:7047:7047 ghcr.io/tlsnotary/tlsn/notary-server:<version>

API Endpoints

Please refer to the list of all HTTP APIs here, and WebSocket APIs here.

PSE Development Notary Server

⚠️ WARNING: notary.pse.dev is hosted for development purposes only. You are welcome to use it for exploration and development; however, please refrain from building your business on it. Use it at your own risk.

The TLSNotary team hosts a public notary server for development, experimentation, and demonstration purposes. The server is currently open to everyone, provided that it is used fairly.

We host multiple versions of the notary server:

VersionNotary URLInfo/StatusGitHubNote
v0.1.0-alpha.7https://notary.pse.dev/v0.1.0-alpha.7info/healthv0.1.0-alpha.7Release notes
v0.1.0-alpha.6https://notary.pse.dev/v0.1.0-alpha.6info/healthv0.1.0-alpha.6Release notes
v0.1.0-alpha.5https://notary.pse.dev/v0.1.0-alpha.5info/healthv0.1.0-alpha.5Release notes
nightlyhttps://notary.pse.dev/nightlyinfo/healthdev

For more details on the deployment, refer to this GitHub Action.

To check the status of the notary server, visit the healthcheck endpoint at: https://notary.pse.dev/<version>/healthcheck

WebSocket Proxy Server

Because web browsers don't have the ability to make TCP connections directly, TLSNotary requires a WebSocket proxy to set up TCP connections when it is used in a browser. To facilitate the exploration of TLSNotary and to run the examples easily, the TLSNotary team hosts a public WebSocket proxy server. This server can be used to access the following whitelisted domains:

api.twitter.com:443
twitter.com:443
gateway.reddit.com:443
reddit.com:443
swapi.dev:443
api.x.com:443
x.com:443
discord.com:443
connect.garmin.com:443
uber.com:443
riders.uber.com:443
m.uber.com:443
wise.com:443
coinbase.com:443
accounts.coinbase.com:443
www.agoda.com:443

You can utilize this WebSocket proxy with the following syntax:

wss://notary.pse.dev/proxy?token=<domain>

Replace <domain> with the domain you wish to access (for example, swapi.dev).

Running Notary Server on Windows Subsystem for Linux (WSL)

When running the Notary Server and WebSocket Proxy on Windows Subsystem for Linux (WSL), you may encounter networking issues. In older versions of Windows (prior to Windows 11 22H2), WSL uses a virtual Ethernet adapter with its own IP address, which requires additional firewall configuration.

For Windows Versions Prior to 11 22H2:

  1. Identify the WSL IP Address:
    Run the following command inside the WSL terminal:

    wsl hostname -I
    
  2. Configure Port Forwarding on the Windows Host:
    To forward traffic from the Windows host to the Notary Server inside WSL, set up port forwarding. Run the following PowerShell command on your Windows host, replacing connectaddress with the WSL IP address you retrieved in the previous step:

    netsh interface portproxy add v4tov4 listenport=7047 listenaddress=0.0.0.0 connectport=7047 connectaddress=192.168.101.100
    

For Windows 11 22H2 and Later:

In newer versions of Windows (Windows 11 22H2 and above), networking has been simplified with the introduction of mirrored mode. This mode allows WSL instances to share the host’s network interface, eliminating the need for manual port forwarding configurations. You can enable mirrored mode as recommended by Microsoft here.

MPC-TLS

During the MPC-TLS phase the Prover and the Verifier run an MPC protocol enabling the Prover to connect to, and exchange data with, a TLS-enabled Server.

Listed below are some key points regarding this protocol:

  • The Verifier only learns the encrypted application data of the TLS session.
  • The Prover is not solely capable of constructing requests, nor can they forge responses from the Server.
  • The protocol enables the Prover to prove the authenticity of the exchanged data to the Verifier.

Handshake

A TLS handshake is the first step in establishing a TLS connection between a Prover and a Server. In TLSNotary the Prover is the one who starts the TLS handshake and physically communicates with the Server, but all cryptographic TLS operations are performed together with the Verifier using MPC.

The Prover and Verifier use a series of MPC protocols to compute the TLS session key in such a way that both only have their share of the key and never learn the full key. Both parties then proceed to complete the TLS handshake using their shares of the key.

See our section on Key Exchange for more details of how this is done.

Note: to a third party observer, the Prover's connection to the server appears like a regular TLS connection and the security guaranteed by TLS remains intact for the Prover.

The only exception is that since the Verifier is a party to the MPC TLS, the security for the Prover against a malicious Verifier is provided by the underlying MPC protocols and not by TLS.

With the shares of the session key computed and the TLS handshake completed, the parties now proceed to the next MPC protocol where they use their session key shares to jointly generate encrypted requests and decrypt server responses while keeping the plaintext of both the requests and responses private from the Verifier.

Encryption, Decryption, and MAC Computation

This section explains how the Prover and Verifier use MPC to encrypt data sent to the server, decrypt data received from the server, and compute the MAC for the ciphertext using MPC. It shows how the Prover and Verifier collaborate to encrypt and decrypt data. The Verifier performs these tasks "blindly", without acquiring knowledge of the plaintext.

Encryption

To encrypt the plaintext, both parties input their TLS key shares as private inputs to the MPC protocol, along with some other public data. Additionally, the Prover inputs her plaintext as a private input.

Encryption

Both parties see the resulting ciphertext and execute the 2PC MAC protocol to compute the MAC for the ciphertext.

The Prover then dispatches the ciphertext and the MAC to the server.

Decryption

Once the Prover receives the ciphertext and its associated MAC from the server, the parties first authenticate the ciphertext by validating the MAC. They do this by running the MPC protocol to compute the authentic MAC for the ciphertext. They then verify if the authentic MAC matches the MAC received from the server.

Next, the parties decrypt the ciphertext by providing their key shares as private inputs to the MPC protocol, along with the ciphertext and some other public data.

Decryption

The resulting plaintext is revealed ONLY to the Prover.

Please note, the actual low-level implementation details of decryption are more nuanced than what we have described here. For more information, please consult Low-level Decryption details.

Notarization

Even though the Prover can prove data provenance directly to the Verifier, in some scenarios it may be beneficial for the Verifier to outsource the verification of the TLS session to a trusted Notary as explained here.

As part of the TLSNotary protocol, the Prover creates authenticated commitments to the plaintext and has the Notary sign them without the Notary ever seeing the plaintext. This offers a way for the Prover to selectively prove the authenticity of arbitrary portions of the plaintext to an application-specific Verifier later.

Please refer to the Commitments section for low-level details on the commitment scheme.

Signing the Session Header

The Notary signs an artifact known as a Session Header, thereby attesting to the authenticity of the plaintext from a TLS session. A Session Header contains a Prover's commitment to the plaintext and a Prover's commitment to TLS-specific data which uniquely identifies the server.

The Prover can later use the signed Session Header to prove data provenance to an application-specific Verifier.

It's important to highlight that throughout the entire TLSNotary protocol, including this signing stage, the Notary does not gain knowledge of either the plaintext or the identity of the server with which the Prover communicated.

Verification

To prove data provenance to a third-party Verifier, the Prover provides the following information:

  • Session Header signed by the Notary
  • opening to the plaintext commitment
  • TLS-specific data which uniquely identifies the server
  • identity of the server

The Verifier performs the following verification steps:

  • verifies that the opening corresponds to the commitment in the Session Header
  • verifies that the TLS-specific data corresponds to the commitment in the Session Header
  • verifies the identity of the server against TLS-specific data

Next, the Verifier parses the opening with an application-specific parser (e.g. HTTP or JSON) to get the final output. Since the Prover is allowed to selectively disclose the data, that data which was not disclosed by the Prover will appear to the Verifier as redacted.

Below is an example of a verification output for an HTTP 1.1 request and response. Note that since the Prover chose not to disclose some sensitive information like their HTTP session token and address, that information will be withheld from the Verifier and will appear to him as redacted (in red).

Verification example

Key Exchange

In TLS, the first step towards obtaining TLS session keys is to compute a shared secret between the client and the server by running the ECDH protocol. The resulting shared secret in TLS terms is called the pre-master secret PMS.

With TLSNotary, at the end of the key exchange, the Server gets the PMS as usual. The Prover and the Verifier, jointly operating as the TLS client, compute additive shares of the PMS. This prevents either party from unilaterally sending or receiving messages with the Server. Subsequently, the authenticity and integrity of the messages are guaranteed to both the Prover and Verifier, while also keeping the plaintext hidden from the Verifier.

The 3-party ECDH protocol between the Server the Prover and the Verifier works as follows:

  1. Server sends its public key to Prover, and Prover forwards it to Verifier
  2. Prover picks a random private key share and computes a public key share
  3. Verifier picks a random private key share and computes a public key share
  4. Verifier sends to Prover who computes and sends to Server
  5. Prover computes an EC point
  6. Verifier computes an EC point
  7. Addition of points and results in the coordinate , which is PMS. (The coordinate is not used in TLS)

Using the notation from here, our goal is to compute in such a way that

  1. Neither party learns the other party's value
  2. Neither party learns , only their respective shares of .

We will use two maliciously secure protocols described on p.25 in the paper Efficient Secure Two-Party Exponentiation:

  • A2M protocol, which converts additive shares into multiplicative shares, i.e. given shares a and b such that a + b = c, it converts them into shares d and e such that d * e = c
  • M2A protocol, which converts multiplicative shares into additive shares

We apply A2M to to get and also we apply A2M to to get . Then the above can be rewritten as:

Then the first party locally computes the first factor and gets , the second party locally computes the second factor and gets . Then we can again rewrite as:

Now we apply M2A to to get , which leads us to two final terms each of which is the share of of the respective party:

Finite-Field Arithmetic

Some protocols used in TLSNotary need to convert two-party sharings of products or sums of some field elements into each other. For this purpose we use share conversion protocols which use oblivious transfer (OT) as a sub-protocol. Here we want to have a closer look at the security guarantees these protocols offer.

Adding covert security

Our goal is to add covert security to our share conversion protocols. This means that we want an honest party to be able to detect a malicious adversary, who is then able to abort the protocol. Our main concern is that the adversary might be able to leak private inputs of the honest party without being noticed. For this reason we require that the adversary cannot do anything which would give him a better chance than guessing the private input at random, which is
guessing bits with a probability of for not being detected.

In the following we want to have a closer look at how the sender and receiver can deviate from the protocol.

Malicious receiver

Note that in our protocol a malicious receiver cannot forge the protocol output, since he does not send anything to the sender during protocol execution. Even when this protocol is embedded into an outer protocol, where at some point the receiver has to open his output or a computation involving it, then all he can do is to open an output with , which is just equivalent to changing his input from .

Malicious sender

In the case of a malicious sender the following things can happen:

  1. The sender can impose an arbitrary field element as input onto the receiver without him noticing. To do this he simply sends in every OT, where is i-th bit of .
  2. The sender can execute a selective-failure attack, which allows him to learn any predicate about the receiver's input. For each OT round , the sender alters one of the OT values to be , where . This will cause that in the end the equation no longer holds but only if the forged OT value has actually been picked by the receiver.
  3. The sender does not use a random number generator with a seed to sample the masks , instead he simply chooses them at will.

M2A Protocol Review

Without loss of generality let us recall the Multiplication-To-Addition (M2A) protocol, but our observations also apply to the Addition-To-Multiplication (A2M) protocol, which is very similar. We start with a short review of the M2A protocol.

Let there be a sender with some field element and some receiver with another field element . After protocol execution the sender ends up with and the receiver ends up with , so that .

  • - rng seed
  • - bit-length of elements in
  • - bit-length of rng seed

OT Sender

with input

  1. Sample some random masks:
  2. For every compute:
  3. Compute new share:
  4. Send OTs to receiver:

OT Receiver

with input

  1. Set (from OT)
  2. Compute new share:

Replay protocol

In order to mitigate the mentioned protocol deviations in the case of a malicious sender we will introduce a replay protocol.

In this section we will use capital letters for values sent in the replay protocol, which in the case of an honest sender are equal to their lowercase counterparts.

The idea for the replay protocol is that at some point after the conversion protocol, the sender has to reveal the rng seed and his input to the receiver. In order to do this, he will send and to the receiver after the conversion protocol has been executed. If the sender is honest then of course and . The receiver can then check if the value he picked during protocol execution does match what he can now reconstruct from and , i.e. that .

Using this replay protocol the sender at some point reveals all his secrets because he sends his rng seed and protocol input to the receiver. This means that we can only use covertly secure share conversion with replay as a sub-protocol if it is acceptable for the outer protocol, that the input to share-conversion becomes public at some later point.

Now in practice we often want to execute several rounds of share-conversion, as we need to convert several field elements. Because of this we let the sender use the same rng seed to seed his rng once and then he uses this rng instance for all protocol rounds. This means we have protocol executions , and all masks produced from this rng seed . So the sender will write his seed and all the to some tape, which in the end is sent to the receiver. As a security precaution we also let the sender commit to his rng seed before the first protocol execution. In detail:

Sender
  1. Sender has some inputs and picks some rng seed .
  2. Sender commits to his rng seed and sends the commitment to the receiver.
  3. Sender sends all his OTs for protocol executions.
  4. Sender sends tape which contains the rng seed and all the .
Receiver
  1. Receiver checks that is indeed the committed rng seed.
  2. For every protocol execution the receiver checks that .

Having a look at the ways a malicious sender could cheat from earlier, we notice:

  1. The sender can no longer impose an arbitrary field element onto the receiver, because the receiver would notice that during the replay.
  2. The sender can still carry out a selective-failure attack, but this is equivalent to guessing bits of at random with a probability of for being undetected.
  3. The sender is now forced to use an rng seed to produce the masks, because during the replay, these masks are reproduced from and indirectly checked via .

Dual Execution with Asymmetric Privacy

TLSNotary uses the DEAP protocol described below to ensure malicious security of the overall protocol.

When using DEAP in TLSNotary, the User plays the role of Alice and has full privacy and the Notary plays the role of Bob and reveals all of his private inputs after the TLS session with the server is over. The Notary's private input is his TLS session key share.

The parties run the Setup and Execution steps of DEAP but they defer the Equality Check. Since during the Equality Check all of the Notary's secrets are revealed to User, it must be deferred until after the TLS session with the server is over, otherwise the User would learn the full TLS session keys and be able to forge the TLS transcript.

Introduction

Malicious secure 2-party computation with garbled circuits typically comes at the expense of dramatically lower efficiency compared to execution in the semi-honest model. One technique, called Dual Execution [MF06] [HKE12], achieves malicious security with a minimal 2x overhead. However, it comes with the concession that a malicious adversary may learn bits of the other's input with probability .

We present a variant of Dual Execution which provides different trade-offs. Our variant ensures complete privacy for one party, by sacrificing privacy entirely for the other. Hence the name, Dual Execution with Asymmetric Privacy (DEAP). During the execution phase of the protocol both parties have private inputs. The party with complete privacy learns the authentic output prior to the final stage of the protocol. In the final stage, prior to the equality check, one party reveals their private input. This allows a series of consistency checks to be performed which guarantees that the equality check can not cause leakage.

Similarly to standard DualEx, our variant ensures output correctness and detects leakage (of the revealing parties input) with probability where is the number of bits leaked.

Preliminary

The protocol takes place between Alice and Bob who want to compute where and are Alice and Bob's inputs respectively. The privacy of Alice's input is ensured, while Bob's input will be revealed in the final steps of the protocol.

Premature Leakage

Firstly, our protocol assumes a small amount of premature leakage of Bob's input is tolerable. By premature, we mean prior to the phase where Bob is expected to reveal his input.

If Alice is malicious, she has the opportunity to prematurely leak bits of Bob's input with probability of it going undetected.

Aborts

We assume that it is acceptable for either party to cause the protocol to abort at any time, with the condition that no information of Alice's inputs are leaked from doing so.

Committed Oblivious Transfer

In the last phase of our protocol Bob must open all oblivious transfers he sent to Alice. To achieve this, we require a very relaxed flavor of committed oblivious transfer. For more detail on these relaxations see section 2 of Zero-Knowledge Using Garbled Circuits [JKO13].

Notation

  • and are Alice and Bob's inputs, respectively.
  • denotes an encoding of chosen by Alice.
  • and are Alice and Bob's encoded active inputs, respectively, ie .
  • denotes a binding commitment to
  • denotes a garbled circuit for computing , where:
    • .
  • denotes output decoding information where
  • denotes the global offset of a garbled circuit where
  • denotes a secure pseudo-random generator
  • denotes a secure hash function

Protocol

The protocol can be thought of as three distinct phases: The setup phase, execution, and equality-check.

Setup

  1. Alice creates a garbled circuit with corresponding input labels , and output label commitment .
  2. Bob creates a garbled circuit with corresponding input labels .
  3. For committed OT, Bob picks a seed and uses it to generate all random-tape for his OTs with . Bob sends to Alice.
  4. Alice retrieves her active input labels from Bob using OT.
  5. Bob retrieves his active input labels from Alice using OT.
  6. Alice sends , , and to Bob.
  7. Bob sends and to Alice.

Execution

Both Alice and Bob can execute this phase of the protocol in parallel as described below:

Alice

  1. Evaluates using and to acquire .
  2. Defines .
  3. Computes a commitment where is a key only known to Alice. She sends this commitment to Bob.
  4. Waits to receive from Bob1.
  5. Checks that is authentic, aborting if not, then decodes to using .

At this stage, a malicious Bob has learned nothing and Alice has obtained the output which she knows to be authentic.

Bob

  1. Evaluates using and to acquire . He checks against the commitment which Alice sent earlier, aborting if it is invalid.
  2. Decodes to using which he received earlier. He defines and stores it for the equality check later.
  3. Sends to Alice1.
  4. Receives from Alice and stores it for the equality check later.

Bob, even if malicious, has learned nothing except the purported output and is not convinced it is correct. In the next phase Alice will attempt to convince Bob that it is.

Alice, if honest, has learned the correct output thanks to the authenticity property of garbled circuits. Alice, if malicious, has potentially learned Bob's entire input .

1

This is a significant deviation from standard DualEx protocols such as [HKE12]. Typically the output labels are not returned to the Generator, instead, output authenticity is established during a secure equality check at the end. See the section below for more detail.

Equality Check

  1. Bob opens his garbled circuit and OT by sending , and to Alice.
  2. Alice, can now derive the purported input labels to Bob's garbled circuit .
  3. Alice uses to open all of Bob's OTs for and verifies that they were performed honestly. Otherwise she aborts.
  4. Alice verifies that was garbled honestly by checking . Otherwise she aborts.
  5. Alice now opens by sending and to Bob.
  6. Bob verifies then asserts , aborting otherwise.

Bob is now convinced that is correct, ie . Bob is also assured that Alice only learned up to k bits of his input prior to revealing, with a probability of of it being undetected.

Analysis

Malicious Alice

On the Leakage of Corrupted Garbled Circuits [DPB18] is recommended reading on this topic.

During the first execution, Alice has some degrees of freedom in how she garbles . According to [DPB18], when using a modern garbling scheme such as [ZRE15], these corruptions can be analyzed as two distinct classes: detectable and undetectable.

Recall that our scheme assumes Bob's input is an ephemeral secret which can be revealed at the end. For this reason, we are entirely unconcerned about the detectable variety. Simply providing Bob with the output labels commitment is sufficient to detect these types of corruptions. In this context, our primary concern is regarding the correctness of the output of .

[DPB18] shows that any undetectable corruption made to is constrained to the arbitrary insertion or removal of NOT gates in the circuit, such that computes instead of . Note that any corruption of has an equivalent effect. [DPB18] also shows that Alice's ability to exploit this is constrained by the topology of the circuit.

Recall that in the final stage of our protocol Bob checks that the output of matches the output of , or more specifically:

For the moment we'll assume Bob garbles honestly and provides the same inputs for both evaluations.

In the scenario where Bob reveals the output of prior to Alice committing to there is a trivial adaptive attack available to Alice. As an extreme example, assume Alice could choose such that . For most practical functions this is not possible to garble without detection, but for the sake of illustration we humor the possibility. In this case she could simply compute where in order to pass the equality check.

To address this, Alice is forced to choose , and prior to Bob revealing the output. In this case it is obvious that any valid combination of must satisfy all constraints on . Thus, for any non-trivial , choosing a valid combination would be equivalent to guessing correctly. In which case, any attack would be detected by the equality check with probability where k is the number of guessed bits of . This result is acceptable within our model as explained earlier.

Malicious Bob

Zero-Knowledge Using Garbled Circuits [JKO13] is recommended reading on this topic.

The last stage of our variant is functionally equivalent to the protocol described in [JKO13]. After Alice evaluates and commits to , Bob opens his garbled circuit and all OTs entirely. Following this, Alice performs a series of consistency checks to detect any malicious behavior. These consistency checks do not depend on any of Alice's inputs, so any attempted selective failure attack by Bob would be futile.

Bob's only options are to behave honestly, or cause Alice to abort without leaking any information.

Malicious Alice & Bob

They deserve whatever they get.

Encryption

Here we will explain our protocol for 2PC encryption using a block cipher in counter-mode.

Our documentation on Dual Execution with Asymmetric Privacy is recommended prior reading for this section.

Preliminary

Ephemeral Keyshare

It is important to recognise that the Notary's keyshare is an ephemeral secret. It is only private for the duration of the User's TLS session, after which the User is free to learn it without affecting the security of the protocol.

It is this fact which allows us to achieve malicious security for relatively low cost. More details on this here.

Premature Leakage

A small amount of undetected premature keyshare leakage is quite tolerable. For example, if the Notary leaks 3 bits of their keyshare, it gives the User no meaningful advantage in any attack, as she could have simply guessed the bits correctly with probability and mounted the same attack. Assuming a sufficiently long cipher key is used, eg. 128 bits, this is not a concern.

The equality check at the end of our protocol ensures that premature leakage is detected with a probability of where k is the number of leaked bits. The Notary is virtually guaranteed to detect significant leakage and can abort prior to notarization.

Plaintext Leakage

Our protocol assures no leakage of the plaintext to the Notary during both encryption and decryption. The Notary reveals their keyshare at the end of the protocol, which allows the Notary to open their garbled circuits and oblivious transfers completely to the User. The User can then perform a series of consistency checks to ensure that the Notary behaved honestly. Because these consistency checks do not depend on any inputs of the User, aborting does not reveal any sensitive information (in contrast to standard DualEx which does).

Integrity

During the entirety of the TLS session the User performs the role of the garbled circuit generator, thus ensuring that a malicious Notary can not corrupt or otherwise compromise the integrity of messages sent to/from the Server.

Notation

  • is one block of plaintext
  • is the corresponding block of ciphertext, ie
  • is the cipher key
  • is the counter block
  • and denote the User and Notary cipher keyshares, respectively, where
  • is a mask randomly selected by the User
  • is the encrypted counter-block, ie
  • denotes the block cipher used by the TLS session
  • denotes a binding commitment to the value
  • denotes a garbled encoding of chosen by party

Encryption Protocol

The encryption protocol uses DEAP without any special variations. The User and Notary directly compute the ciphertext for each block of a message the User wishes to send to the Server:

The User creates a commitment to the plaintext active labels for the Notary's circuit where is a random key known only to the User. The User sends this commitment to the Notary to be used in the authdecode protocol later. It's critical that the User commits to prior to the Notary revealing in the final phase of DEAP. This ensures that if is a commitment to valid labels, then it must be a valid commitment to the plaintext . This is because learning the complementary wire label for any bit of prior to learning is virtually impossible.

Decryption Protocol

The protocol for decryption is very similar but has some key differences to encryption.

For decryption, DEAP is used for every block of the ciphertext to compute the masked encrypted counter-block:

This mask , chosen by the User, hides from the Notary and thus the plaintext too. Conversely, the User can simply remove this mask in order to compute the plaintext .

Following this, the User can retrieve the wire labels from the Notary using OT.

Similarly to the procedure for encryption, the User creates a commitment where is a random key known only to the User. The User sends this commitment to the Notary to be used in the authdecode protocol later.

Proving the validity of

In addition to computing the masked encrypted counter-block, the User must also prove that the labels they chose afterwards actually correspond to the ciphertext sent by the Server.

This is can be done efficiently in one execution using the zero-knowledge protocol described in [JKO13] the same as we do in the final phase of DEAP.

The Notary garbles a circuit which computes:

Notice that the User and Notary will already have computed when they computed earlier. Conveniently, the Notary can re-use the garbled labels as input labels for this circuit. For more details on the reuse of garbled labels see [AMR17].

Computing MAC in 2PC

  1. What is a MAC
  2. How a MAC is computed in AES-GCM
  3. Computing MAC using secure two-party computation (2PC)

1. What is a MAC

When sending an encrypted ciphertext to the Webserver, the User attaches a checksum to it. The Webserver uses this checksum to check whether the ciphertext has been tampered with while in transit. This checksum is known as the "authentication tag" and also as the "Message Authentication Code" (MAC).

In order to create a MAC for some ciphertext not only the ciphertext but also some secret key is used as an input. This makes it impossible to forge some ciphertext without knowing the secret key.

The first few paragraphs of this article explain what would happen if there was no MAC: it would be possible for a malicious actor to modify the plaintext by flipping certain bits of the ciphertext.

2. How a MAC is computed in AES-GCM

In TLS the plaintext is split up into chunks called "TLS records". Each TLS record is encrypted and a MAC is computed for the ciphertext. The MAC (in AES-GCM) is obtained by XORing together the GHASH output and the GCTR output. Let's see how each of those outputs is computed:

2.1 GCTR output

The GCTR output is computed by simply AES-ECB encrypting a counter block with the counter set to 1 (the iv, nonce and AES key are the same as for the rest of the TLS record).

2.2 GHASH output

The GHASH output is the output of the GHASH function described in the NIST publication in section 6.4 in this way: "In effect, the GHASH function calculates ". and are elements of the extension field .

  • "β€’" is a special type of multiplication called multiplication in a finite field described in section 6.3 of the NIST publication.
  • βŠ• is addition in a finite field and it is defined as XOR.

In other words, GHASH splits up the ciphertext into 16-byte blocks, each block is numbered etc. There's also which is called the GHASH key, which just is the AES-encrypted zero-block. We need to raise to as many powers as there are blocks, i.e. if we have 5 blocks then we need 5 powers: . Each block is multiplied by the corresponding power and all products are summed together.

Below is the pseudocode for multiplying two 128-bit field elements x and y in :

1. result = 0
2. R = 0xE1000000000000000000000000000000
3. bit_length = 128
4. for i=0 upto bit_length-1
5.    if y[i] == 1
6.       result ^= x
7. x = (x >> 1) ^ ((x & 1) * R)
8. return result

Standard math properties hold in finite field math, viz. commutative: and distributive: .

3. Computing MAC using secure two-party computation (2PC)

The goal of the protocol is to compute the MAC in such a way that neither party would learn the other party's share of i.e. the GHASH key share. At the start of the protocol each party has:

  1. ciphertext blocks .
  2. XOR share of : the User has and the Notary has .
  3. XOR share of the GCTR output: the User has and the Notary has .

Note that 2. and 3. were obtained at an earlier stage of the TLSNotary protocol.

3.1 Example with a single ciphertext block

To illustrate what we want to achieve, we consider the case of just having a single ciphertext block . The GHASH_output will be:

The User and the Notary will compute locally the left and the right terms respectively. Then each party will XOR their result to the GCTR output share and will get their XOR share of the MAC:

User :

Notary:

Finally, the Notary sends to the User who obtains:

For longer ciphertexts, the problem is that higher powers of the hashkey cannot be computed locally, because we deal with additive sharings, i.e..

3.2 Computing ciphertexts with an arbitrary number of blocks

We now introduce our 2PC MAC protocol for computing ciphertexts with an arbitrary number of blocks. Our protocol can be divided into the following steps.

Steps
  1. First, both parties convert their additive shares and into multiplicative shares and .
  2. This allows each party to locally compute the needed higher powers of these multiplicative shares, i.e for blocks of ciphertext:
    • the user computes
    • the notary computes
  3. Then both parties convert each of these multiplicative shares back to additive shares
    • the user ends up with
    • the notary ends up with
  4. Each party can now locally compute their additive MAC share .

The conversion steps (1 and 3) require communication between the user and the notary. They will use A2M (Addition-to-Multiplication) and M2A (Multiplication-to-Addition) protocols, which make use of oblivious transfer, to convert the shares. The user will be the sender and the notary the receiver.

2PC MAC Overview

3.2.1 (A2M) Convert additive shares of H into multiplicative share

At first (step 1) we have to get a multiplicative share of , so that notary and user can locally compute the needed higher powers. For this we use an adapted version of the A2M protocol in chapter 4 of Efficient Secure Two-Party Exponentiation.

The user will decompose his share into individual oblivious transfers , where

  • is some random value used for all oblivious transfers
  • is a random mask used per oblivious transfer, with
  • depending on the receiver's choice.

The notary's choice in the i-th OT will depend on the bit value in the i-th position of his additive share . In the end the multiplicative share of the user will simply be the inverse of the random value, and the notary will sum all his OT outputs, so that all the will vanish and hence he gets his multiplicative share .

3.2.2 (M2A) Convert multiplicative shares into additive shares

In step 3 of our protocol, we use the oblivious transfer method described in chapter 4.1 of the Gilboa paper Two Party RSA Key Generation to convert all the multiplicative shares back into additive shares . We only show how the method works for the share , because it is the same for higher powers.

The user will be the OT sender and decompose his shares into individual oblivious transfers , where , depending on the receiver's choices. Each of these OTs is masked with a random value . He will then obliviously send them to the notary. Depending on the binary representation of his multiplicative share, the notary will choose one of the choices and do this for all 128 oblivious transfers.

After that the user will locally XOR all his and end up with his additive share , and the notary will do the same for all the results of the oblivious transfers and get .

3.3 Free Squaring

In the actual implementation of the protocol we only compute odd multiplicative shares, i.e. , so that we only need to share these odd shares in step 3. This is possible because we can compute even additive shares from odd additive shares. We observe that for even :

So we only need to convert odd multiplicative shares into odd additive shares, which gives us a 50% reduction in cost. The remaining even additive shares can then be computed locally.

3.3 Creating a robust protocol

Both the A2M and M2A protocols on their own only provide semi-honest security. They are secure against a malicious receiver, but the sender has degrees of freedom to cause leakage of the MAC keyshares. However, for our purposes this does not present a problem as long as leakage is detected.

To detect a malicious sender, we require the sender to commit to the PRG seed used to generate the random values in the share conversion protocols. After the TLS session is closed the MAC keyshares are no longer secret, which allows the sender to reveal this seed to the receiver. Subsequently, the receiver can perform a consistency check to make sure the sender followed the protocol honestly.

3.3.1 Malicious notary

The protocol is secure against a malicious notary, because he is the OT receiver, which means that there is actually no input from him during the protocol execution except for the final MAC output. He just receives the OT input from the user, so the only thing he can do is to provide a wrong MAC keyshare. This will cause the server to reject the MAC when the user sends the request. The protocol simply aborts.

3.3.2 Malicious user

A malicious user could actually manipulate what he sends in the OT and potentially endanger the security of the protocol by leaking the notary's MAC key. To address this we force the user to reveal his MAC key after the server response so that the notary can check for the correctness of the whole MAC 2PC protocol. Then if the notary detects that the user cheated, he would simply abort the protocol.

The only problem when doing this is, that we want the whole TLSNotary protocol to work under the assumption that the notary can intercept the traffic between the user and the server. This would allow the notary to trick the user into thinking that the TLS session is already terminated, if he can force the server to respond. The user would send his MAC key share too early and the notary could, now having the complete MAC key, forge the ciphertext and create a valid MAC for it. He would then send this forged request to the server and forward the response of the server to the user.

To prevent this scenario we need to make sure that the TLS connection to the server is terminated before the user sends his MAC key share to the notary. Following the TLS RFC, we leverage close_notify to ensure all messages sent to the server have been processed and the connection is closed. Unfortunately, many server TLS implementations do not support close_notify. In these cases we instead send an invalid message to the server which forces it to respond with a fatal alert message and close the connection.

Commitments

Here we illustrate the commitment scheme used to create authenticated commitments to the plaintext in scenarios where a general-purpose Notary is used. (Note that this scheme is not used when the Prover proves directly to the Verifier)

A naive approach of extending the Encryption and Decryption steps to also compute a commitment (e.g. BLAKE3 hash) using MPC is too resource-intensive, prompting us to provide a more lightweight commitment scheme.

The high-level idea is that the Prover creates a commitment to the active plaintext encoding from the MPC protocol used for Encryption and Decryption.

We also hide the amount of commitments (to preserve Prover privacy) by having the Prover commit to the Merkle tree of commitments.

Commitment

Chrome Extension (MV3) for TLSNotary

Important

⚠️ When running the extension against a notary server, ensure that the notary server's version matches the version of this extension.

The TLSNotary browser extension includes a plugin system that allows you to safely extend its functionality with custom plugins tailored to your specific data sources. This section also explains how to interact with the TLSN Extension within web applications.

Browser Extension Plugins

The TLSN Extension has a plugin system that allows you to safely extend its functionality. The plugin system is based on Extism, which enables you to write plugins in the programming language of your choice. This page focuses on plugins written in TypeScript.

What Can You Do with Plugins?

Plugins can add new custom features to the extension by using built-in host functions, such as:

  • Requesting private information from the browser, such as cookies and headers of one or more hostnames.
  • Submitting a new notarization request.
  • Redirecting a browsing window.

New features and capabilities will be added based on feedback from developers. Please reach out to us on Discord.

Templates and Examples

You can find a boilerplate template at tlsn-plugin-boilerplate, which is a great starting point. This repository explains how to compile and test Typescript plugins.

The examples folder contains more examples of TLSNotary plugins.

Configuration JSON

A plugin must include a configuration JSON file that describes its behavior and permissions.

export type PluginConfig = {
  title: string;           // The name of the plugin
  description: string;     // A description of the plugin's purpose
  icon?: string;           // A base64-encoded image string representing the plugin's icon (optional)
  steps?: StepConfig[];    // An array describing the UI steps and behavior (see Step UI below) (optional)
  hostFunctions?: string[];// Host functions that the plugin will have access to
  cookies?: string[];      // Cookies the plugin will have access to, cached by the extension from specified hosts (optional)
  headers?: string[];      // Headers the plugin will have access to, cached by the extension from specified hosts (optional)
  requests: { method: string; url: string }[]; // List of requests that the plugin is allowed to make
  notaryUrls?: string[];   // List of notary services that the plugin is allowed to use (optional)
  proxyUrls?: string[];    // List of websocket proxies that the plugin is allowed to use (optional)
};

Step UI

The plugin system allows customization of the UI and the functionality of the side panel.

Step Configuration

The steps are declared in the JSON configuration:

type StepConfig = {
  title: string;         // Text for the step's title
  description?: string;  // OPTIONAL: Text for the step's description
  cta: string;           // Text for the step's call-to-action button
  action: string;        // The function name that this step will execute
  prover?: boolean;      // Boolean indicating if this step outputs a notarization
}

You need to implement the functionality of the steps in src/index.ts. The function names must match the corresponding step names in the JSON configuration.

Host Functions

Host functions are specific behaviors provided by the extension that the plugin can call. Host function usage may vary depending on the language used to write the plugin.

redirect

Redirects the current tab to a different URL.

Example in JavaScript:

const { redirect } = Host.getFunctions();
const mem = Memory.fromString('https://x.com');
redirect(mem.offset);

notarize

Notarizes a request.

Example in JavaScript:

const { notarize } = Host.getFunctions();
const mem = Memory.fromString(JSON.stringify({
  url: "https://...",
  method: "GET",
  headers: {
    "authorization": "Bearer xxx",
    "cookie": "lang=en; auth_token=xxx",
  },
  secretHeaders: [
    "authorization: Bearer xxx",
    "cookie: lang=en; auth_token=xxx",
  ],
  getSecretBody: "parseResponse" // See redaction example below
}));
const idOffset = notarize(mem.offset);
const id = Memory.find(idOffset).readString();
Host.outputString(JSON.stringify(id)); // Outputs the notarization ID

Redaction

If the getSecretResponse field of the notarize host function call is set, the corresponding method will be called to parse the response of the request. Make sure to also export this function in the main module declaration in index.d.ts.

function parseResponse() {
  const bodyString = Host.inputString();
  const params = JSON.parse(bodyString);
  const revealed = `"screen_name":"${params.screen_name}"`;
  const selectionStart = bodyString.indexOf(revealed);
  const selectionEnd = selectionStart + revealed.length;
  const secretResps = [
    bodyString.substring(0, selectionStart),
    bodyString.substring(selectionEnd, bodyString.length),
  ];
  Host.outputString(JSON.stringify(secretResps));
}

TLSN Extension's Provider API

This page is a reference for the TLSN Extension's Provider API. This API can be used in web pages to request attestation history, notarize requests, manage plugins, and more from the TLSN extension.

The TLSN Extension injects a provider API into websites visited by its users using the window.tlsn provider object. This allows webpages to connect to the TLSN Extension and query TLSN attestations, with the user's permission, of course.

Connect to TLSN Extension

tlsn.connect()

This method is used to request a connection between the website and the extension. Once connected, the website can use the provider API to request actions from the extension.

Parameters

None.

Returns

A promise that resolves to the full provider API object.

Example

const client = await tlsn.connect();

Screenshot

Screenshot 2024-07-04 at 2 22 00β€―PM

Provider API Methods

client.getHistory(method, url, metadata)

This method is used to request attestation history from the extension.

Parameters

  1. method: A glob pattern matching the request method of the proof. (e.g., get, {get,post}, *)
  2. url: A glob pattern matching the request URL of the proof. (e.g., **, https://swapi.dev/**)
  3. metadata: An object containing glob patterns that match metadata about the request. (e.g., {id: "swapi-proof-1"})

Returns

A promise that resolves to an array of proof header data.

type ProofHeader = {
  id: string;
  method: string;
  notaryUrl: string;
  time: string;
  url: string;
  websocketProxyUrl: string;
}

Example

const proofs = await client.getHistory('*', '**', {id: '0x1234567890'});

Screenshot

Screenshot 2024-07-04 at 2 41 56β€―PM

client.getProof(id)

This method is used to request the full data of a specific proof by its ID.

Parameters

  1. id: The ID of the proof.

Returns

A promise that resolves to the proof data or null.

type ProofData = {
  notaryUrl: string;
  session: Session; // https://github.com/tlsnotary/tlsn-js/blob/main/src/types.ts#L7-L11;
  substrings: Substrings; // https://github.com/tlsnotary/tlsn-js/blob/main/src/types.ts#L73-L76
}

Example

const proof = await client.getProof('FE512M1.72007336176400000000000000000000');

Screenshot

Screenshot 2024-07-04 at 2 44 00β€―PM

client.notarize(url, requestOptions, proofConfig)

This method is used to request notarization of a specific request.

Parameters

  1. url: The URL of the request.
  2. requestOptions: An object containing the following:
    1. method: GET, POST, PUT, etc.
    2. headers: A map of headers.
    3. body: The string content of the request body.
  3. proofConfig: An object containing the following:
    1. notaryUrl: URL of the notary (defaults to the extension's setting).
    2. websocketProxyUrl: URL of the websocket proxy (defaults to the extension's setting).
    3. maxSentData: Maximum allowed sent data (defaults to the extension's setting).
    4. maxRecvData: Maximum allowed received data (defaults to the extension's setting).
    5. maxTranscriptSize: Maximum allowed transcript size (defaults to the extension's setting).
    6. metadata: An object containing metadata.

Returns

A promise that resolves to the proof data.

type ProofData = {
  notaryUrl: string;
  session: Session; // https://github.com/tlsnotary/tlsn-js/blob/main/src/types.ts#L7-L11;
  substrings: Substrings; // https://github.com/tlsnotary/tlsn-js/blob/main/src/types.ts#L73-L76
}

Example

const proof = await client.notarize(
  'https://swapi.dev/api/planets/9',
  {
    method: 'get',
    headers: {
      "Accept": "application/json",
      "Accept-Encoding": "identity",
      "Connection": "close",
      "Cookie": "csrftoken=blahblahblah",
    }
  },
  {
    metadata: {id: 'test-1'},
  }
);

Screenshot

Screenshot 2024-07-04 at 2 54 48β€―PM

client.installPlugin(url, metadata)

This method is used to request the installation of a plugin.

Parameters

  1. url: The URL to the plugin's WASM file.
  2. metadata: An object containing metadata about the plugin.

Returns

A promise that resolves to the plugin ID.

Example

const pluginId = await client.installPlugin(
  'https://github.com/tlsnotary/tlsn-extension/raw/main/plugins/twitter_profile/index.wasm',
  { id: 'demo-plugin-1' }
);

Screenshot

Screenshot 2024-07-04 at 3 05 54β€―PM

client.getPlugins(url, origin, metadata)

This method is used to query installed plugins.

Parameters

  1. url: A glob pattern matching the URL to the plugin's WASM file.
  2. origin: A glob pattern matching the origin requesting the plugin installation.
  3. metadata: An object containing glob patterns matching metadata about the plugin.

Returns

A promise that resolves to the plugin configuration.

type PluginConfig = {
  hash: string;
  title: string;
  description: string;
  icon?: string;
  steps?: StepConfig[];
  hostFunctions?: string[];
  cookies?: string[];
  headers?: string[];
  requests: { method: string; url: string }[];
  notaryUrls?: string[];
  proxyUrls?: string[];
};

Example

const plugin = await client.getPlugins('**', 'https://swapi.dev', {id: 'demo-plugin-1'});

Screenshot

Screenshot 2024-07-04 at 3 23 14β€―PM

client.runPlugin(id)

This method is used to request the execution of a plugin.

Parameters

  1. id: The ID of the plugin.

Returns

A promise that resolves to the proof data.

type ProofData = {
  notaryUrl: string;
  session: Session; // https://github.com/tlsnotary/tlsn-js/blob/main/src/types.ts#L7-L11;
  substrings: Substrings; // https://github.com/tlsnotary/tlsn-js/blob/main/src/types.ts#L73-L76
}

Example

const plugin = await client.runPlugin("6931d2ad63340d3a1fb1a5c1e3f4454c5a518164d6de5ad272e744832355ee02");

Screenshot

Screenshot 2024-07-04 at 3 24 09β€―PM

Glossary

TermExplanation
2PCSecure Two-party computation
A2MAddition-to-Multiplication
AESAdvanced Encryption Standard
DEAPDual Execution with Asymmetric Privacy
ECBElectronic codebook (encryption mode)
ECDHElliptic-Curve Diffie-Hellman
GCGarbled Circuit
GCMGalois/Counter Mode
GHASHGCM hash
HMACHash-based Message Authentication Code
M2aMultiplication-to-Addition
MACMessage Authentication Code
MPCSecure Multi-party computation
OToblivious transfer
PMSPre master secret (TLS)
PRFPseudo Random Function
PRGpseudorandom generator
PSEPrivacy and Scaling Exploration
RSARivest–Shamir–Adleman (public-key cryptosystem)
TLStransport layer security