Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: tlsn-core #213

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions tlsn-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,16 @@ edition = "2021"
thiserror = "1"
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3.3"
p256 = { version = "0.10", features = ["ecdsa", "serde"]}
webpki = { version = "0.22.0", features = ["alloc"]}
webpki-roots = "0.22.5"
x509-parser = "0.14.0"
blake3 = "1.3.3"
rs_merkle = "1.2.0"
rand_chacha = "0.3"
rand = "0.8"
rand_core = "0.6"

[dev-dependencies]
rstest = "0.12"
hex = "0.4"
78 changes: 14 additions & 64 deletions tlsn-core/src/commitment.rs
Original file line number Diff line number Diff line change
@@ -1,74 +1,24 @@
use crate::{transcript::TranscriptRange, HashCommitment};
use serde::Serialize;

/// A User's commitment to a portion of the notarized data
#[derive(Serialize, Clone, Default)]
pub struct Commitment {
/// The actual commitment
commitment: HashCommitment,
typ: CommitmentType,
direction: Direction,
/// The index of this commitment in the Merkle tree of commitments
merkle_tree_index: u32,
/// The absolute byte ranges within the notarized data. The committed data
/// is located in those ranges. Ranges do not overlap.
ranges: Vec<TranscriptRange>,
#[derive(Serialize, Clone)]
pub enum Commitment {
Blake3(Blake3),
}

impl Commitment {
pub fn new(
typ: CommitmentType,
direction: Direction,
commitment: HashCommitment,
ranges: Vec<TranscriptRange>,
merkle_tree_index: u32,
) -> Self {
Self {
typ,
direction,
commitment,
ranges,
merkle_tree_index,
}
}

pub fn typ(&self) -> &CommitmentType {
&self.typ
}

pub fn direction(&self) -> &Direction {
&self.direction
}

pub fn merkle_tree_index(&self) -> u32 {
self.merkle_tree_index
}

pub fn commitment(&self) -> [u8; 32] {
self.commitment
}

pub fn ranges(&self) -> &[TranscriptRange] {
&self.ranges
impl Default for Commitment {
fn default() -> Self {
Commitment::Blake3(Blake3::default())
}
}

#[derive(Clone, PartialEq, Serialize, Default)]
#[allow(non_camel_case_types)]
pub enum CommitmentType {
#[default]
// A blake3 digest of the garbled circuit's active labels. The labels are generated from a PRG seed.
// For more details on the protocol used to generate this commitment, see
// https://github.com/tlsnotary/docs-mdbook/blob/main/src/protocol/notarization/public_data_commitment.md
labels_blake3,
/// A blake3 digest of the encoding of the plaintext
#[derive(Serialize, Clone, Default)]
pub struct Blake3 {
labels_hash: [u8; 32],
}

#[derive(Serialize, Clone, PartialEq, Default, Debug)]
/// A TLS transcript consists of a stream of bytes which were `Sent` to the server
/// and a stream of bytes which were `Received` from the server . The User creates
/// separate commitments to bytes in each direction.
pub enum Direction {
#[default]
Sent,
Received,
impl Blake3 {
pub fn labels_hash(&self) -> &[u8; 32] {
&self.labels_hash
}
}
110 changes: 110 additions & 0 deletions tlsn-core/src/encoder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
//! Adapted from tlsn/mpc/mpc-core, except [encode() in](ChaChaEncoder) was modified to encode 1 bit
//! at a time
use crate::{session_header::LabelSeed, transcript::TranscriptRange};
use rand::{CryptoRng, Rng, SeedableRng};
use rand_chacha::ChaCha20Rng;
use std::ops::BitXor;

const DELTA_STREAM_ID: u64 = u64::MAX;
/// PLAINTEXT_STREAM_ID must match the id of the plaintext input in tls/tls-circuits/src/c6.rs
const PLAINTEXT_STREAM_ID: u64 = 4;

#[derive(Clone, Copy)]
pub struct Block(u128);

impl Block {
#[inline]
pub fn new(b: u128) -> Self {
Self(b)
}

#[inline]
pub fn random<R: Rng + CryptoRng + ?Sized>(rng: &mut R) -> Self {
Self::new(rng.gen())
}

#[inline]
pub fn set_lsb(&mut self) {
self.0 |= 1;
}

#[inline]
pub fn inner(&self) -> u128 {
self.0
}
}

impl BitXor for Block {
type Output = Self;

#[inline]
fn bitxor(self, other: Self) -> Self::Output {
Self(self.0 ^ other.0)
}
}

/// Global binary offset used by the Free-XOR technique to create wire label
/// pairs where W_1 = W_0 ^ Delta.
///
/// In accordance with the (p&p) permute-and-point technique, the LSB of delta is set to 1 so
/// the permute bit LSB(W_1) = LSB(W_0) ^ 1
#[derive(Clone, Copy)]
pub struct Delta(Block);

impl Delta {
/// Creates new random Delta
pub(crate) fn random<R: Rng + CryptoRng + ?Sized>(rng: &mut R) -> Self {
let mut block = Block::random(rng);
block.set_lsb();
Self(block)
}

/// Returns the inner block
#[inline]
pub(crate) fn into_inner(self) -> Block {
self.0
}
}

/// Encodes wires into labels using the ChaCha algorithm.
pub struct ChaChaEncoder {
rng: ChaCha20Rng,
delta: Delta,
}

impl ChaChaEncoder {
/// Creates a new encoder with the provided seed
///
/// * `seed` - 32-byte seed for ChaChaRng
pub fn new(seed: LabelSeed) -> Self {
let mut rng = ChaCha20Rng::from_seed(seed);

// Stream id u64::MAX is reserved to generate delta.
// This way there is only ever 1 delta per seed
rng.set_stream(DELTA_STREAM_ID);
let delta = Delta::random(&mut rng);

Self { rng, delta }
}

/// Encodes one bit of plaintext into two labels
///
/// * `pos` - The position of a bit which needs to be encoded
pub fn encode(&mut self, pos: usize) -> [Block; 2] {
self.rng.set_stream(PLAINTEXT_STREAM_ID);

// jump to the multiple-of-128 bit offset (128 bits is the size of one label)
// (the argument to `set_word_pos()` is a 32-bit word)
self.rng.set_word_pos((pos as u128) * 4);

let zero_label = Block::random(&mut self.rng);

[zero_label, zero_label ^ self.delta.into_inner()]
}

/// Return labels corresponding to the opening bytes in the given ranges
pub fn get_labels(&self, opening: &[u8], ranges: &[TranscriptRange]) -> Vec<Block> {
// TODO: not implemented
vec![Block::new(0); 32]
}
}
34 changes: 34 additions & 0 deletions tlsn-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,38 @@ pub enum Error {
SerializationError,
#[error("Attempted to create an invalid range")]
RangeInvalid,
#[error("The header is expected to contain a signature")]
SignatureInHeaderExpected,
#[error("Can't verify the document because either signature or pubkey were not provided")]
NoPubkeyOrSignature,
#[error("The document is expected to contain a signature")]
SignatureExpected,
#[error("The document is NOT expected to contain a signature")]
SignatureNotExpected,
#[error("x509-parser error: {0}")]
X509ParserError(String),
#[error("webpki error: {0}")]
WebpkiError(String),
#[error("Certificate chain was empty")]
EmptyCertificateChain,
#[error("End entity must not be a certificate authority")]
EndEntityIsCA,
#[error("Key exchange data was signed using an unknown curve")]
UnknownCurveInKeyExchange,
#[error("Key exchange data was signed using an unknown algorithm")]
UnknownSigningAlgorithmInKeyExchange,
#[error("Commitment verification failed")]
CommitmentVerificationFailed,
#[error("Error while performing validation check in: {0}")]
ValidationCheckError(String),
#[error("Failed to verify a Merkle proof")]
MerkleProofVerificationFailed,
#[error("Overlapping openings don't match")]
OverlappingOpeningsDontMatch,
#[error("Failed while checking committed TLS")]
CommittedTLSCheckFailed,
#[error("An internal error occured")]
InternalError,
#[error("Error during signature verification")]
SignatureVerificationError,
}
57 changes: 44 additions & 13 deletions tlsn-core/src/handshake_data.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::error::Error;
use crate::{error::Error, utils::blake3, webpki_utils, HashCommitment, SessionHeader};
use serde::Serialize;

/// an x509 certificate in DER format
Expand Down Expand Up @@ -37,24 +37,55 @@ impl HandshakeData {
}
}

pub fn serialize(&self) -> Result<Vec<u8>, Error> {
bincode::serialize(&self).map_err(|_| Error::SerializationError)
/// Creates a hash commitment to `self`
pub fn commit(&self) -> Result<HashCommitment, Error> {
let msg = self.serialize()?;
Ok(blake3(&msg))
}

pub fn tls_cert_chain(&self) -> &[CertDER] {
&self.tls_cert_chain
}
/// Verifies the TLS document against the DNS name `dns_name`:
/// - end entity certificate was issued to `dns_name` and was valid at the time of the
/// notarization
/// - certificate chain was signed by a trusted certificate authority
/// - key exchange parameters were signed by the end entity certificate
/// - commitment to misc TLS data is correct
///
pub fn verify(self, header: &SessionHeader, dns_name: &str) -> Result<(), Error> {
// Verify TLS certificate chain against local root certs. Some certs in the chain may
// have expired at the time of this verification. We verify their validity at the time
// of notarization.
webpki_utils::verify_cert_chain(&self.tls_cert_chain, header.handshake_summary().time())?;

pub fn sig_ke_params(&self) -> &ServerSignature {
&self.sig_ke_params
}
let ee_cert = webpki_utils::extract_end_entity_cert(&self.tls_cert_chain)?;

// check that TLS key exchange parameters were signed by the end-entity cert
webpki_utils::verify_sig_ke_params(
&ee_cert,
&self.sig_ke_params,
header.handshake_summary().ephemeral_ec_pubkey(),
&self.client_random,
&self.server_random,
)?;

pub fn client_random(&self) -> &[u8] {
&self.client_random
webpki_utils::check_dns_name_present_in_cert(&ee_cert, dns_name)?;

// Create a commitment and compare it to the value committed to earlier
let expected = HandshakeData::new(
self.tls_cert_chain,
self.sig_ke_params,
self.client_random,
self.server_random,
)
.commit()?;
if &expected != header.handshake_summary().handshake_commitment() {
return Err(Error::CommitmentVerificationFailed);
}

Ok(())
}

pub fn server_random(&self) -> &[u8] {
&self.server_random
fn serialize(&self) -> Result<Vec<u8>, Error> {
bincode::serialize(&self).map_err(|_| Error::SerializationError)
}
}

Expand Down
20 changes: 10 additions & 10 deletions tlsn-core/src/handshake_summary.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
use crate::HashCommitment;
use crate::{pubkey::PubKey, HashCommitment};
use serde::Serialize;

#[derive(Clone, Serialize, Default)]
pub struct HandshakeSummary {
/// notarization time against which the TLS Certificate validity is checked
time: u64,
/// ephemeral pubkey for ECDH key exchange
ephemeral_ec_pubkey: EphemeralECPubkey,
ephemeral_ec_pubkey: PubKey,
/// User's commitment to [crate::handshake_data::HandshakeData]
handshake_commitment: HashCommitment,
}

impl HandshakeSummary {
pub fn new(
time: u64,
ephemeral_ec_pubkey: EphemeralECPubkey,
ephemeral_ec_pubkey: PubKey,
handshake_commitment: HashCommitment,
) -> Self {
Self {
Expand All @@ -28,7 +28,7 @@ impl HandshakeSummary {
self.time
}

pub fn ephemeral_ec_pubkey(&self) -> &EphemeralECPubkey {
pub fn ephemeral_ec_pubkey(&self) -> &PubKey {
&self.ephemeral_ec_pubkey
}

Expand All @@ -39,24 +39,24 @@ impl HandshakeSummary {

/// Types of the ephemeral EC pubkey currently supported by TLSNotary
#[derive(Clone, Serialize, Default)]
pub enum EphemeralECPubkeyType {
pub enum EphemeralKeyType {
#[default]
P256,
}

/// The ephemeral EC public key (part of the TLS key exchange parameters)
#[derive(Clone, Serialize, Default)]
pub struct EphemeralECPubkey {
typ: EphemeralECPubkeyType,
pub struct EphemeralKey {
typ: EphemeralKeyType,
pubkey: Vec<u8>,
}

impl EphemeralECPubkey {
pub fn new(typ: EphemeralECPubkeyType, pubkey: Vec<u8>) -> Self {
impl EphemeralKey {
pub fn new(typ: EphemeralKeyType, pubkey: Vec<u8>) -> Self {
Self { typ, pubkey }
}

pub fn typ(&self) -> &EphemeralECPubkeyType {
pub fn typ(&self) -> &EphemeralKeyType {
&self.typ
}

Expand Down
Loading