refactor(core): decouple attestation from core api (#875)

* refactor(core): decouple attestation from core api

* remove dead test

* fix encoding tree test

* clippy

* fix comment
This commit is contained in:
sinu.eth
2025-05-22 09:00:43 -07:00
committed by GitHub
parent 555f65e6b2
commit 8b1cac6fe0
42 changed files with 1210 additions and 1187 deletions

View File

@@ -8,7 +8,7 @@ use crate::{
};
use tls_core::verify::WebPkiVerifier;
use tlsn_common::config::ProtocolConfigValidator;
use tlsn_core::CryptoProvider;
use tlsn_core::{CryptoProvider, VerifyConfig};
use tlsn_server_fixture_certs::CA_CERT_DER;
use tlsn_verifier::{Verifier, VerifierConfig};
@@ -100,7 +100,9 @@ async fn run_instance<S: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>
.build()?,
);
verifier.verify(io.compat()).await?;
verifier
.verify(io.compat(), &VerifyConfig::default())
.await?;
println!("verifier done");

View File

@@ -1,6 +1,6 @@
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
use tlsn_common::config::ProtocolConfig;
use tlsn_core::{transcript::Idx, CryptoProvider};
use tlsn_core::{CryptoProvider, ProveConfig};
use tlsn_prover::{Prover, ProverConfig};
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
@@ -111,15 +111,19 @@ pub async fn run_prover(
Ok::<(), anyhow::Error>(())
};
let (prover_task, _) = try_join(prover_fut.map_err(anyhow::Error::from), tls_fut).await?;
let mut prover = prover_task.start_prove();
let (mut prover, _) = try_join(prover_fut.map_err(anyhow::Error::from), tls_fut).await?;
let (sent_len, recv_len) = prover.transcript().len();
prover
.prove_transcript(Idx::new(0..sent_len), Idx::new(0..recv_len))
.await?;
prover.finalize().await?;
let mut builder = ProveConfig::builder(prover.transcript());
builder.reveal_sent(&(0..sent_len)).unwrap();
builder.reveal_recv(&(0..recv_len)).unwrap();
let config = builder.build().unwrap();
prover.prove(&config).await?;
prover.close().await?;
Ok(())
}

View File

@@ -25,6 +25,7 @@ derive_builder = { workspace = true }
futures = { workspace = true }
once_cell = { workspace = true }
opaque-debug = { workspace = true }
rand = { workspace = true }
rangeset = { workspace = true }
serio = { workspace = true, features = ["codec", "bincode"] }
thiserror = { workspace = true }

View File

@@ -3,14 +3,27 @@
use std::ops::Range;
use mpz_common::Context;
use mpz_core::Block;
use mpz_memory_core::{
binary::U8,
correlated::{Delta, Key, Mac},
Vector,
};
use rand::Rng;
use serde::{Deserialize, Serialize};
use serio::{stream::IoStreamExt, SinkExt};
use tlsn_core::transcript::{
encoding::{new_encoder, Encoder, EncoderSecret, EncodingProvider, EncodingProviderError},
Direction,
use tlsn_core::{
hash::HashAlgorithm,
transcript::{
encoding::{
new_encoder, Encoder, EncoderSecret, EncodingCommitment, EncodingProvider,
EncodingProviderError, EncodingTree, EncodingTreeError,
},
Direction, Idx,
},
};
use crate::transcript::TranscriptRefs;
/// Bytes of encoding, per byte.
const ENCODING_SIZE: usize = 128;
@@ -23,22 +36,29 @@ struct Encodings {
/// Transfers the encodings using the provided seed and keys.
///
/// The keys must be consistent with the global delta used in the encodings.
pub async fn transfer(
pub async fn transfer<'a>(
ctx: &mut Context,
secret: &EncoderSecret,
sent_keys: impl IntoIterator<Item = &'_ Block>,
recv_keys: impl IntoIterator<Item = &'_ Block>,
) -> Result<(), EncodingError> {
let encoder = new_encoder(secret);
refs: &TranscriptRefs,
delta: &Delta,
f: impl Fn(Vector<U8>) -> &'a [Key],
) -> Result<EncodingCommitment, EncodingError> {
let secret = EncoderSecret::new(rand::rng().random(), delta.as_block().to_bytes());
let encoder = new_encoder(&secret);
let sent_keys: Vec<u8> = sent_keys
.into_iter()
.flat_map(|key| key.as_bytes())
let sent_keys: Vec<u8> = refs
.sent()
.iter()
.copied()
.flat_map(&f)
.flat_map(|key| key.as_block().as_bytes())
.copied()
.collect();
let recv_keys: Vec<u8> = recv_keys
.into_iter()
.flat_map(|key| key.as_bytes())
let recv_keys: Vec<u8> = refs
.recv()
.iter()
.copied()
.flat_map(&f)
.flat_map(|key| key.as_block().as_bytes())
.copied()
.collect();
@@ -75,26 +95,40 @@ pub async fn transfer(
})
.await?;
Ok(())
let root = ctx.io_mut().expect_next().await?;
ctx.io_mut().send(secret.clone()).await?;
Ok(EncodingCommitment {
root,
secret: secret.clone(),
})
}
/// Receives the encodings using the provided MACs.
///
/// The MACs must be consistent with the global delta used in the encodings.
pub async fn receive(
pub async fn receive<'a>(
ctx: &mut Context,
sent_macs: impl IntoIterator<Item = &'_ Block>,
recv_macs: impl IntoIterator<Item = &'_ Block>,
) -> Result<impl EncodingProvider, EncodingError> {
hasher: &(dyn HashAlgorithm + Send + Sync),
refs: &TranscriptRefs,
f: impl Fn(Vector<U8>) -> &'a [Mac],
idxs: impl IntoIterator<Item = &(Direction, Idx)>,
) -> Result<(EncodingCommitment, EncodingTree), EncodingError> {
let Encodings { mut sent, mut recv } = ctx.io_mut().expect_next().await?;
let sent_macs: Vec<u8> = sent_macs
.into_iter()
let sent_macs: Vec<u8> = refs
.sent()
.iter()
.copied()
.flat_map(&f)
.flat_map(|mac| mac.as_bytes())
.copied()
.collect();
let recv_macs: Vec<u8> = recv_macs
.into_iter()
let recv_macs: Vec<u8> = refs
.recv()
.iter()
.copied()
.flat_map(&f)
.flat_map(|mac| mac.as_bytes())
.copied()
.collect();
@@ -127,7 +161,17 @@ pub async fn receive(
.zip(recv_macs)
.for_each(|(enc, mac)| *enc ^= mac);
Ok(Provider { sent, recv })
let provider = Provider { sent, recv };
let tree = EncodingTree::new(hasher, idxs, &provider)?;
let root = tree.root();
ctx.io_mut().send(root.clone()).await?;
let secret = ctx.io_mut().expect_next().await?;
let commitment = EncodingCommitment { root, secret };
Ok((commitment, tree))
}
#[derive(Debug)]
@@ -177,6 +221,8 @@ enum ErrorRepr {
expected: usize,
got: usize,
},
#[error("encoding tree error: {0}")]
EncodingTree(EncodingTreeError),
}
impl From<std::io::Error> for EncodingError {
@@ -184,3 +230,9 @@ impl From<std::io::Error> for EncodingError {
Self(ErrorRepr::Io(value))
}
}
impl From<EncodingTreeError> for EncodingError {
fn from(value: EncodingTreeError) -> Self {
Self(ErrorRepr::EncodingTree(value))
}
}

View File

@@ -1,9 +1,13 @@
//! TLS transcript.
use mpz_memory_core::{binary::U8, Vector};
use mpz_memory_core::{
binary::{Binary, U8},
MemoryExt, Vector,
};
use mpz_vm_core::{Vm, VmError};
use rangeset::Intersection;
use tls_core::msgs::enums::ContentType;
use tlsn_core::transcript::{Direction, Idx, Transcript};
use tlsn_core::transcript::{Direction, Idx, PartialTranscript, Transcript};
/// A transcript of sent and received TLS records.
#[derive(Debug, Default, Clone)]
@@ -168,6 +172,69 @@ impl TranscriptRefs {
#[error("not all application plaintext was committed to in the TLS transcript")]
pub struct IncompleteTranscript {}
/// Decodes the transcript.
pub fn decode_transcript(
vm: &mut dyn Vm<Binary>,
sent: &Idx,
recv: &Idx,
refs: &TranscriptRefs,
) -> Result<(), VmError> {
let sent_refs = refs.get(Direction::Sent, sent).expect("index is in bounds");
let recv_refs = refs
.get(Direction::Received, recv)
.expect("index is in bounds");
for slice in sent_refs.into_iter().chain(recv_refs) {
// Drop the future, we don't need it.
drop(vm.decode(slice)?);
}
Ok(())
}
/// Verifies a partial transcript.
pub fn verify_transcript(
vm: &mut dyn Vm<Binary>,
transcript: &PartialTranscript,
refs: &TranscriptRefs,
) -> Result<(), InconsistentTranscript> {
let sent_refs = refs
.get(Direction::Sent, transcript.sent_authed())
.expect("index is in bounds");
let recv_refs = refs
.get(Direction::Received, transcript.received_authed())
.expect("index is in bounds");
let mut authenticated_data = Vec::new();
for data in sent_refs.into_iter().chain(recv_refs) {
let plaintext = vm
.get(data)
.expect("reference is valid")
.expect("plaintext is decoded");
authenticated_data.extend_from_slice(&plaintext);
}
let mut purported_data = Vec::with_capacity(authenticated_data.len());
for range in transcript.sent_authed().iter_ranges() {
purported_data.extend_from_slice(&transcript.sent_unsafe()[range]);
}
for range in transcript.received_authed().iter_ranges() {
purported_data.extend_from_slice(&transcript.received_unsafe()[range]);
}
if purported_data != authenticated_data {
return Err(InconsistentTranscript {});
}
Ok(())
}
/// Error for [`verify_transcript`].
#[derive(Debug, thiserror::Error)]
#[error("inconsistent transcript")]
pub struct InconsistentTranscript {}
#[cfg(test)]
mod tests {
use super::TranscriptRefs;

View File

@@ -46,7 +46,7 @@ use crate::{
merkle::MerkleTree,
presentation::PresentationBuilder,
signing::{Signature, VerifyingKey},
transcript::encoding::EncodingCommitment,
transcript::TranscriptCommitment,
CryptoProvider,
};
@@ -150,8 +150,8 @@ pub struct Body {
connection_info: Field<ConnectionInfo>,
server_ephemeral_key: Field<ServerEphemKey>,
cert_commitment: Field<ServerCertCommitment>,
encoding_commitment: Option<Field<EncodingCommitment>>,
extensions: Vec<Field<Extension>>,
transcript_commitments: Vec<Field<TranscriptCommitment>>,
}
impl Body {
@@ -195,8 +195,8 @@ impl Body {
connection_info: conn_info,
server_ephemeral_key,
cert_commitment,
encoding_commitment,
extensions,
transcript_commitments,
} = self;
let mut fields: Vec<(FieldId, Hash)> = vec![
@@ -212,14 +212,11 @@ impl Body {
),
];
if let Some(encoding_commitment) = encoding_commitment {
fields.push((
encoding_commitment.id,
hasher.hash_separated(&encoding_commitment.data),
));
for field in extensions.iter() {
fields.push((field.id, hasher.hash_separated(&field.data)));
}
for field in extensions.iter() {
for field in transcript_commitments.iter() {
fields.push((field.id, hasher.hash_separated(&field.data)));
}
@@ -242,9 +239,9 @@ impl Body {
&self.cert_commitment.data
}
/// Returns the encoding commitment.
pub(crate) fn encoding_commitment(&self) -> Option<&EncodingCommitment> {
self.encoding_commitment.as_ref().map(|field| &field.data)
/// Returns the transcript commitments.
pub(crate) fn transcript_commitments(&self) -> impl Iterator<Item = &TranscriptCommitment> {
self.transcript_commitments.iter().map(|field| &field.data)
}
}

View File

@@ -4,15 +4,15 @@ use rand::{rng, Rng};
use crate::{
attestation::{
Attestation, AttestationConfig, Body, EncodingCommitment, Extension, FieldId, FieldKind,
Header, ServerCertCommitment, VERSION,
Attestation, AttestationConfig, Body, Extension, FieldId, Header, ServerCertCommitment,
VERSION,
},
connection::{ConnectionInfo, ServerEphemKey},
hash::{HashAlgId, TypedHash},
hash::HashAlgId,
request::Request,
serialize::CanonicalSerialize,
signing::SignatureAlgId,
transcript::encoding::EncoderSecret,
transcript::TranscriptCommitment,
CryptoProvider,
};
@@ -27,9 +27,8 @@ pub struct Sign {
connection_info: Option<ConnectionInfo>,
server_ephemeral_key: Option<ServerEphemKey>,
cert_commitment: ServerCertCommitment,
encoding_commitment_root: Option<TypedHash>,
encoder_secret: Option<EncoderSecret>,
extensions: Vec<Extension>,
transcript_commitments: Vec<TranscriptCommitment>,
}
/// An attestation builder.
@@ -59,7 +58,6 @@ impl<'a> AttestationBuilder<'a, Accept> {
signature_alg,
hash_alg,
server_cert_commitment: cert_commitment,
encoding_commitment_root,
extensions,
} = request;
@@ -77,17 +75,6 @@ impl<'a> AttestationBuilder<'a, Accept> {
));
}
if encoding_commitment_root.is_some()
&& !config
.supported_fields()
.contains(&FieldKind::EncodingCommitment)
{
return Err(AttestationBuilderError::new(
ErrorKind::Request,
"encoding commitment is not supported",
));
}
if let Some(validator) = config.extension_validator() {
validator(&extensions)
.map_err(|err| AttestationBuilderError::new(ErrorKind::Extension, err))?;
@@ -101,8 +88,7 @@ impl<'a> AttestationBuilder<'a, Accept> {
connection_info: None,
server_ephemeral_key: None,
cert_commitment,
encoding_commitment_root,
encoder_secret: None,
transcript_commitments: Vec::new(),
extensions,
},
})
@@ -122,18 +108,21 @@ impl AttestationBuilder<'_, Sign> {
self
}
/// Sets the encoder secret.
pub fn encoder_secret(&mut self, secret: EncoderSecret) -> &mut Self {
self.state.encoder_secret = Some(secret);
self
}
/// Adds an extension to the attestation.
pub fn extension(&mut self, extension: Extension) -> &mut Self {
self.state.extensions.push(extension);
self
}
/// Sets the transcript commitments.
pub fn transcript_commitments(
&mut self,
transcript_commitments: Vec<TranscriptCommitment>,
) -> &mut Self {
self.state.transcript_commitments = transcript_commitments;
self
}
/// Builds the attestation.
pub fn build(self, provider: &CryptoProvider) -> Result<Attestation, AttestationBuilderError> {
let Sign {
@@ -142,9 +131,8 @@ impl AttestationBuilder<'_, Sign> {
connection_info,
server_ephemeral_key,
cert_commitment,
encoding_commitment_root,
encoder_secret,
extensions,
transcript_commitments,
} = self.state;
let hasher = provider.hash.get(&hash_alg).map_err(|_| {
@@ -162,19 +150,6 @@ impl AttestationBuilder<'_, Sign> {
)
})?;
let encoding_commitment = if let Some(root) = encoding_commitment_root {
let Some(secret) = encoder_secret else {
return Err(AttestationBuilderError::new(
ErrorKind::Field,
"encoding commitment requested but encoder_secret was not set",
));
};
Some(EncodingCommitment { root, secret })
} else {
None
};
let mut field_id = FieldId::default();
let body = Body {
@@ -186,11 +161,14 @@ impl AttestationBuilder<'_, Sign> {
AttestationBuilderError::new(ErrorKind::Field, "handshake data was not set")
})?),
cert_commitment: field_id.next(cert_commitment),
encoding_commitment: encoding_commitment.map(|commitment| field_id.next(commitment)),
extensions: extensions
.into_iter()
.map(|extension| field_id.next(extension))
.collect(),
transcript_commitments: transcript_commitments
.into_iter()
.map(|commitment| field_id.next(commitment))
.collect(),
};
let header = Header {
@@ -269,9 +247,7 @@ mod test {
use crate::{
connection::{HandshakeData, HandshakeDataV1_2},
fixtures::{
encoder_secret, encoding_provider, request_fixture, ConnectionFixture, RequestFixture,
},
fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
hash::Blake3,
transcript::Transcript,
};
@@ -346,36 +322,6 @@ mod test {
assert!(err.is_request());
}
#[rstest]
fn test_attestation_builder_accept_unsupported_encoding_commitment() {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
let connection = ConnectionFixture::tlsnotary(transcript.length());
let RequestFixture { request, .. } = request_fixture(
transcript,
encoding_provider(GET_WITH_HEADER, OK_JSON),
connection,
Blake3::default(),
Vec::new(),
);
let attestation_config = AttestationConfig::builder()
.supported_signature_algs([SignatureAlgId::SECP256K1])
.supported_fields([
FieldKind::ConnectionInfo,
FieldKind::ServerEphemKey,
FieldKind::ServerIdentityCommitment,
])
.build()
.unwrap();
let err = Attestation::builder(&attestation_config)
.accept_request(request)
.err()
.unwrap();
assert!(err.is_request());
}
#[rstest]
fn test_attestation_builder_sign_missing_signer(attestation_config: &AttestationConfig) {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
@@ -396,49 +342,10 @@ mod test {
let mut provider = CryptoProvider::default();
provider.signer.set_secp256r1(&[42u8; 32]).unwrap();
let err = attestation_builder.build(&provider).err().unwrap();
let err = attestation_builder.build(&provider).unwrap_err();
assert!(matches!(err.kind, ErrorKind::Config));
}
#[rstest]
fn test_attestation_builder_sign_missing_encoding_seed(
attestation_config: &AttestationConfig,
crypto_provider: &CryptoProvider,
) {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
let connection = ConnectionFixture::tlsnotary(transcript.length());
let RequestFixture { request, .. } = request_fixture(
transcript,
encoding_provider(GET_WITH_HEADER, OK_JSON),
connection.clone(),
Blake3::default(),
Vec::new(),
);
let mut attestation_builder = Attestation::builder(attestation_config)
.accept_request(request)
.unwrap();
let ConnectionFixture {
connection_info,
server_cert_data,
..
} = connection;
let HandshakeData::V1_2(HandshakeDataV1_2 {
server_ephemeral_key,
..
}) = server_cert_data.handshake;
attestation_builder
.connection_info(connection_info)
.server_ephemeral_key(server_ephemeral_key);
let err = attestation_builder.build(crypto_provider).err().unwrap();
assert!(matches!(err.kind, ErrorKind::Field));
}
#[rstest]
fn test_attestation_builder_sign_missing_server_ephemeral_key(
attestation_config: &AttestationConfig,
@@ -463,11 +370,9 @@ mod test {
connection_info, ..
} = connection;
attestation_builder
.connection_info(connection_info)
.encoder_secret(encoder_secret());
attestation_builder.connection_info(connection_info);
let err = attestation_builder.build(crypto_provider).err().unwrap();
let err = attestation_builder.build(crypto_provider).unwrap_err();
assert!(matches!(err.kind, ErrorKind::Field));
}
@@ -500,11 +405,9 @@ mod test {
..
}) = server_cert_data.handshake;
attestation_builder
.server_ephemeral_key(server_ephemeral_key)
.encoder_secret(encoder_secret());
attestation_builder.server_ephemeral_key(server_ephemeral_key);
let err = attestation_builder.build(crypto_provider).err().unwrap();
let err = attestation_builder.build(crypto_provider).unwrap_err();
assert!(matches!(err.kind, ErrorKind::Field));
}
@@ -572,8 +475,7 @@ mod test {
attestation_builder
.connection_info(connection_info)
.server_ephemeral_key(server_ephemeral_key)
.encoder_secret(encoder_secret());
.server_ephemeral_key(server_ephemeral_key);
let attestation = attestation_builder.build(crypto_provider).unwrap();

View File

@@ -1,20 +1,13 @@
use std::{fmt::Debug, sync::Arc};
use crate::{
attestation::{Extension, FieldKind, InvalidExtension},
attestation::{Extension, InvalidExtension},
hash::{HashAlgId, DEFAULT_SUPPORTED_HASH_ALGS},
signing::SignatureAlgId,
};
type ExtensionValidator = Arc<dyn Fn(&[Extension]) -> Result<(), InvalidExtension> + Send + Sync>;
const DEFAULT_SUPPORTED_FIELDS: &[FieldKind] = &[
FieldKind::ConnectionInfo,
FieldKind::ServerEphemKey,
FieldKind::ServerIdentityCommitment,
FieldKind::EncodingCommitment,
];
#[derive(Debug)]
#[allow(dead_code)]
enum ErrorKind {
@@ -52,7 +45,6 @@ impl AttestationConfigError {
pub struct AttestationConfig {
supported_signature_algs: Vec<SignatureAlgId>,
supported_hash_algs: Vec<HashAlgId>,
supported_fields: Vec<FieldKind>,
extension_validator: Option<ExtensionValidator>,
}
@@ -70,10 +62,6 @@ impl AttestationConfig {
&self.supported_hash_algs
}
pub(crate) fn supported_fields(&self) -> &[FieldKind] {
&self.supported_fields
}
pub(crate) fn extension_validator(&self) -> Option<&ExtensionValidator> {
self.extension_validator.as_ref()
}
@@ -84,7 +72,6 @@ impl Debug for AttestationConfig {
f.debug_struct("AttestationConfig")
.field("supported_signature_algs", &self.supported_signature_algs)
.field("supported_hash_algs", &self.supported_hash_algs)
.field("supported_fields", &self.supported_fields)
.finish_non_exhaustive()
}
}
@@ -93,7 +80,6 @@ impl Debug for AttestationConfig {
pub struct AttestationConfigBuilder {
supported_signature_algs: Vec<SignatureAlgId>,
supported_hash_algs: Vec<HashAlgId>,
supported_fields: Vec<FieldKind>,
extension_validator: Option<ExtensionValidator>,
}
@@ -102,7 +88,6 @@ impl Default for AttestationConfigBuilder {
Self {
supported_signature_algs: Vec::default(),
supported_hash_algs: DEFAULT_SUPPORTED_HASH_ALGS.to_vec(),
supported_fields: DEFAULT_SUPPORTED_FIELDS.to_vec(),
extension_validator: Some(Arc::new(|e| {
if !e.is_empty() {
Err(InvalidExtension::new(
@@ -135,12 +120,6 @@ impl AttestationConfigBuilder {
self
}
/// Sets the supported attestation fields.
pub fn supported_fields(&mut self, supported_fields: impl Into<Vec<FieldKind>>) -> &mut Self {
self.supported_fields = supported_fields.into();
self
}
/// Sets the extension validator.
///
/// # Example
@@ -169,7 +148,6 @@ impl AttestationConfigBuilder {
Ok(AttestationConfig {
supported_signature_algs: self.supported_signature_algs.clone(),
supported_hash_algs: self.supported_hash_algs.clone(),
supported_fields: self.supported_fields.clone(),
extension_validator: self.extension_validator.clone(),
})
}
@@ -180,7 +158,6 @@ impl Debug for AttestationConfigBuilder {
f.debug_struct("AttestationConfigBuilder")
.field("supported_signature_algs", &self.supported_signature_algs)
.field("supported_hash_algs", &self.supported_hash_algs)
.field("supported_fields", &self.supported_fields)
.finish_non_exhaustive()
}
}

View File

@@ -18,7 +18,7 @@ use crate::{
signing::SignatureAlgId,
transcript::{
encoding::{EncoderSecret, EncodingProvider, EncodingTree},
Transcript, TranscriptCommitConfigBuilder,
Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
},
CryptoProvider,
};
@@ -195,7 +195,6 @@ pub fn request_fixture(
&encoding_hasher,
transcripts_commitment_config.iter_encoding(),
&encodings_provider,
&transcript.length(),
)
.unwrap();
@@ -211,8 +210,7 @@ pub fn request_fixture(
request_builder
.server_name(server_name)
.server_cert_data(server_cert_data)
.transcript(transcript)
.encoding_tree(encoding_tree.clone());
.transcript(transcript);
let (request, _) = request_builder.build(&provider).unwrap();
@@ -227,7 +225,7 @@ pub fn attestation_fixture(
request: Request,
connection: ConnectionFixture,
signature_alg: SignatureAlgId,
secret: EncoderSecret,
transcript_commitments: &[TranscriptCommitment],
) -> Attestation {
let ConnectionFixture {
connection_info,
@@ -259,7 +257,7 @@ pub fn attestation_fixture(
attestation_builder
.connection_info(connection_info)
.server_ephemeral_key(server_ephemeral_key)
.encoder_secret(secret);
.transcript_commitments(transcript_commitments.to_vec());
attestation_builder.build(&provider).unwrap()
}

View File

@@ -192,3 +192,222 @@ pub mod transcript;
pub use provider::CryptoProvider;
pub use secrets::Secrets;
use rangeset::ToRangeSet;
use serde::{Deserialize, Serialize};
use crate::{
connection::{ServerCertData, ServerName},
transcript::{
Direction, Idx, PartialTranscript, Transcript, TranscriptCommitConfig,
TranscriptCommitRequest, TranscriptCommitment, TranscriptSecret,
},
};
/// Configuration to prove information to the verifier.
#[derive(Debug, Clone)]
pub struct ProveConfig {
server_identity: bool,
transcript: Option<PartialTranscript>,
transcript_commit: Option<TranscriptCommitConfig>,
}
impl ProveConfig {
/// Creates a new builder.
pub fn builder(transcript: &Transcript) -> ProveConfigBuilder {
ProveConfigBuilder::new(transcript)
}
/// Returns `true` if the server identity is to be proven.
pub fn server_identity(&self) -> bool {
self.server_identity
}
/// Returns the transcript to be proven.
pub fn transcript(&self) -> Option<&PartialTranscript> {
self.transcript.as_ref()
}
/// Returns the transcript commitment configuration.
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
self.transcript_commit.as_ref()
}
}
/// Builder for [`ProveConfig`].
#[derive(Debug)]
pub struct ProveConfigBuilder<'a> {
transcript: &'a Transcript,
server_identity: bool,
reveal_sent: Idx,
reveal_recv: Idx,
transcript_commit: Option<TranscriptCommitConfig>,
}
impl<'a> ProveConfigBuilder<'a> {
/// Creates a new builder.
pub fn new(transcript: &'a Transcript) -> Self {
Self {
transcript,
server_identity: false,
reveal_sent: Idx::default(),
reveal_recv: Idx::default(),
transcript_commit: None,
}
}
/// Proves the server identity.
pub fn server_identity(&mut self) -> &mut Self {
self.server_identity = true;
self
}
/// Configures transcript commitments.
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
self.transcript_commit = Some(transcript_commit);
self
}
/// Reveals the given ranges of the transcript.
pub fn reveal(
&mut self,
direction: Direction,
ranges: &dyn ToRangeSet<usize>,
) -> Result<&mut Self, ProveConfigBuilderError> {
let idx = Idx::new(ranges.to_range_set());
if idx.end() > self.transcript.len_of_direction(direction) {
return Err(ProveConfigBuilderError(
ProveConfigBuilderErrorRepr::IndexOutOfBounds {
direction,
actual: idx.end(),
len: self.transcript.len_of_direction(direction),
},
));
}
match direction {
Direction::Sent => self.reveal_sent.union_mut(&idx),
Direction::Received => self.reveal_recv.union_mut(&idx),
}
Ok(self)
}
/// Reveals the given ranges of the sent data transcript.
pub fn reveal_sent(
&mut self,
ranges: &dyn ToRangeSet<usize>,
) -> Result<&mut Self, ProveConfigBuilderError> {
self.reveal(Direction::Sent, ranges)
}
/// Reveals the given ranges of the received data transcript.
pub fn reveal_recv(
&mut self,
ranges: &dyn ToRangeSet<usize>,
) -> Result<&mut Self, ProveConfigBuilderError> {
self.reveal(Direction::Received, ranges)
}
/// Builds the configuration.
pub fn build(self) -> Result<ProveConfig, ProveConfigBuilderError> {
let transcript = if !self.reveal_sent.is_empty() || !self.reveal_recv.is_empty() {
Some(
self.transcript
.to_partial(self.reveal_sent, self.reveal_recv),
)
} else {
None
};
Ok(ProveConfig {
server_identity: self.server_identity,
transcript,
transcript_commit: self.transcript_commit,
})
}
}
/// Error for [`ProveConfigBuilder`].
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct ProveConfigBuilderError(#[from] ProveConfigBuilderErrorRepr);
#[derive(Debug, thiserror::Error)]
enum ProveConfigBuilderErrorRepr {
#[error("range is out of bounds of the transcript ({direction}): {actual} > {len}")]
IndexOutOfBounds {
direction: Direction,
actual: usize,
len: usize,
},
}
/// Configuration to verify information from the prover.
#[derive(Debug, Default, Clone)]
pub struct VerifyConfig {}
impl VerifyConfig {
/// Creates a new builder.
pub fn builder() -> VerifyConfigBuilder {
VerifyConfigBuilder::new()
}
}
/// Builder for [`VerifyConfig`].
#[derive(Debug, Default)]
pub struct VerifyConfigBuilder {}
impl VerifyConfigBuilder {
/// Creates a new builder.
pub fn new() -> Self {
Self {}
}
/// Builds the configuration.
pub fn build(self) -> Result<VerifyConfig, VerifyConfigBuilderError> {
Ok(VerifyConfig {})
}
}
/// Error for [`VerifyConfigBuilder`].
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct VerifyConfigBuilderError(#[from] VerifyConfigBuilderErrorRepr);
#[derive(Debug, thiserror::Error)]
enum VerifyConfigBuilderErrorRepr {}
/// Payload sent to the verifier.
#[doc(hidden)]
#[derive(Debug, Serialize, Deserialize)]
pub struct ProvePayload {
/// Server identity data.
pub server_identity: Option<(ServerName, ServerCertData)>,
/// Transcript data.
pub transcript: Option<PartialTranscript>,
/// Transcript commitment configuration.
pub transcript_commit: Option<TranscriptCommitRequest>,
}
/// Prover output.
pub struct ProverOutput {
/// Transcript commitments.
pub transcript_commitments: Vec<TranscriptCommitment>,
/// Transcript commitment secrets.
pub transcript_secrets: Vec<TranscriptSecret>,
}
opaque_debug::implement!(ProverOutput);
/// Verifier output.
pub struct VerifierOutput {
/// Server identity.
pub server_name: Option<ServerName>,
/// Transcript data.
pub transcript: Option<PartialTranscript>,
/// Transcript commitments.
pub transcript_commitments: Vec<TranscriptCommitment>,
}
opaque_debug::implement!(VerifierOutput);

View File

@@ -84,7 +84,13 @@ impl Presentation {
.transpose()?;
let transcript = transcript
.map(|transcript| transcript.verify_with_provider(provider, &attestation.body))
.map(|transcript| {
transcript.verify_with_provider(
provider,
&attestation.body.connection_info().transcript_length,
attestation.body.transcript_commitments(),
)
})
.transpose()?;
let connection_info = attestation.body.connection_info().clone();

View File

@@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
use crate::{
attestation::{Attestation, Extension},
connection::ServerCertCommitment,
hash::{HashAlgId, TypedHash},
hash::HashAlgId,
signing::SignatureAlgId,
};
@@ -34,7 +34,6 @@ pub struct Request {
pub(crate) signature_alg: SignatureAlgId,
pub(crate) hash_alg: HashAlgId,
pub(crate) server_cert_commitment: ServerCertCommitment,
pub(crate) encoding_commitment_root: Option<TypedHash>,
pub(crate) extensions: Vec<Extension>,
}
@@ -66,20 +65,6 @@ impl Request {
));
}
if let Some(encoding_commitment_root) = &self.encoding_commitment_root {
let Some(encoding_commitment) = attestation.body.encoding_commitment() else {
return Err(InconsistentAttestation(
"encoding commitment is missing".to_string(),
));
};
if &encoding_commitment.root != encoding_commitment_root {
return Err(InconsistentAttestation(
"encoding commitment root does not match".to_string(),
));
}
}
// TODO: improve the O(M*N) complexity of this check.
for extension in &self.extensions {
if !attestation.body.extensions().any(|e| e == extension) {
@@ -102,15 +87,13 @@ pub struct InconsistentAttestation(String);
mod test {
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
use super::*;
use crate::{
connection::{ServerCertOpening, TranscriptLength},
fixtures::{
attestation_fixture, encoder_secret, encoding_provider, request_fixture,
ConnectionFixture, RequestFixture,
attestation_fixture, encoding_provider, request_fixture, ConnectionFixture,
RequestFixture,
},
hash::{Blake3, Hash, HashAlgId},
hash::{Blake3, HashAlgId},
signing::SignatureAlgId,
transcript::Transcript,
CryptoProvider,
@@ -129,12 +112,8 @@ mod test {
Vec::new(),
);
let attestation = attestation_fixture(
request.clone(),
connection,
SignatureAlgId::SECP256K1,
encoder_secret(),
);
let attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
assert!(request.validate(&attestation).is_ok())
}
@@ -152,12 +131,8 @@ mod test {
Vec::new(),
);
let attestation = attestation_fixture(
request.clone(),
connection,
SignatureAlgId::SECP256K1,
encoder_secret(),
);
let attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
request.signature_alg = SignatureAlgId::SECP256R1;
@@ -178,12 +153,8 @@ mod test {
Vec::new(),
);
let attestation = attestation_fixture(
request.clone(),
connection,
SignatureAlgId::SECP256K1,
encoder_secret(),
);
let attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
request.hash_alg = HashAlgId::SHA256;
@@ -204,12 +175,8 @@ mod test {
Vec::new(),
);
let attestation = attestation_fixture(
request.clone(),
connection,
SignatureAlgId::SECP256K1,
encoder_secret(),
);
let attestation =
attestation_fixture(request.clone(), connection, SignatureAlgId::SECP256K1, &[]);
let ConnectionFixture {
server_cert_data, ..
@@ -226,33 +193,4 @@ mod test {
let res = request.validate(&attestation);
assert!(res.is_err())
}
#[test]
fn test_wrong_encoding_commitment_root() {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
let connection = ConnectionFixture::tlsnotary(transcript.length());
let RequestFixture { mut request, .. } = request_fixture(
transcript,
encoding_provider(GET_WITH_HEADER, OK_JSON),
connection.clone(),
Blake3::default(),
Vec::new(),
);
let attestation = attestation_fixture(
request.clone(),
connection,
SignatureAlgId::SECP256K1,
encoder_secret(),
);
request.encoding_commitment_root = Some(TypedHash {
alg: HashAlgId::BLAKE3,
value: Hash::default(),
});
let res = request.validate(&attestation);
assert!(res.is_err())
}
}

View File

@@ -2,7 +2,7 @@ use crate::{
connection::{ServerCertData, ServerCertOpening, ServerName},
request::{Request, RequestConfig},
secrets::Secrets,
transcript::{encoding::EncodingTree, Transcript},
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
CryptoProvider,
};
@@ -11,8 +11,9 @@ pub struct RequestBuilder<'a> {
config: &'a RequestConfig,
server_name: Option<ServerName>,
server_cert_data: Option<ServerCertData>,
encoding_tree: Option<EncodingTree>,
transcript: Option<Transcript>,
transcript_commitments: Vec<TranscriptCommitment>,
transcript_commitment_secrets: Vec<TranscriptSecret>,
}
impl<'a> RequestBuilder<'a> {
@@ -22,8 +23,9 @@ impl<'a> RequestBuilder<'a> {
config,
server_name: None,
server_cert_data: None,
encoding_tree: None,
transcript: None,
transcript_commitments: Vec::new(),
transcript_commitment_secrets: Vec::new(),
}
}
@@ -39,18 +41,23 @@ impl<'a> RequestBuilder<'a> {
self
}
/// Sets the tree to commit to the transcript encodings.
pub fn encoding_tree(&mut self, tree: EncodingTree) -> &mut Self {
self.encoding_tree = Some(tree);
self
}
/// Sets the transcript.
pub fn transcript(&mut self, transcript: Transcript) -> &mut Self {
self.transcript = Some(transcript);
self
}
/// Sets the transcript commitments.
pub fn transcript_commitments(
&mut self,
secrets: Vec<TranscriptSecret>,
commitments: Vec<TranscriptCommitment>,
) -> &mut Self {
self.transcript_commitment_secrets = secrets;
self.transcript_commitments = commitments;
self
}
/// Builds the attestation request and returns the corresponding secrets.
pub fn build(
self,
@@ -60,8 +67,9 @@ impl<'a> RequestBuilder<'a> {
config,
server_name,
server_cert_data,
encoding_tree,
transcript,
transcript_commitments,
transcript_commitment_secrets,
} = self;
let signature_alg = *config.signature_alg();
@@ -84,23 +92,21 @@ impl<'a> RequestBuilder<'a> {
let server_cert_commitment = server_cert_opening.commit(hasher);
let encoding_commitment_root = encoding_tree.as_ref().map(|tree| tree.root());
let extensions = config.extensions().to_vec();
let request = Request {
signature_alg,
hash_alg,
server_cert_commitment,
encoding_commitment_root,
extensions,
};
let secrets = Secrets {
server_name,
server_cert_opening,
encoding_tree,
transcript,
transcript_commitments,
transcript_commitment_secrets,
};
Ok((request, secrets))

View File

@@ -1,4 +1,7 @@
use crate::{attestation::Extension, hash::HashAlgId, signing::SignatureAlgId};
use crate::{
attestation::Extension, hash::HashAlgId, signing::SignatureAlgId,
transcript::TranscriptCommitConfig,
};
/// Request configuration.
#[derive(Debug, Clone)]
@@ -6,6 +9,7 @@ pub struct RequestConfig {
signature_alg: SignatureAlgId,
hash_alg: HashAlgId,
extensions: Vec<Extension>,
transcript_commit: Option<TranscriptCommitConfig>,
}
impl Default for RequestConfig {
@@ -34,6 +38,11 @@ impl RequestConfig {
pub fn extensions(&self) -> &[Extension] {
&self.extensions
}
/// Returns the transcript commitment configuration.
pub fn transcript_commit(&self) -> Option<&TranscriptCommitConfig> {
self.transcript_commit.as_ref()
}
}
/// Builder for [`RequestConfig`].
@@ -42,6 +51,7 @@ pub struct RequestConfigBuilder {
signature_alg: SignatureAlgId,
hash_alg: HashAlgId,
extensions: Vec<Extension>,
transcript_commit: Option<TranscriptCommitConfig>,
}
impl Default for RequestConfigBuilder {
@@ -50,6 +60,7 @@ impl Default for RequestConfigBuilder {
signature_alg: SignatureAlgId::SECP256K1,
hash_alg: HashAlgId::BLAKE3,
extensions: Vec::new(),
transcript_commit: None,
}
}
}
@@ -73,12 +84,19 @@ impl RequestConfigBuilder {
self
}
/// Sets the transcript commitment configuration.
pub fn transcript_commit(&mut self, transcript_commit: TranscriptCommitConfig) -> &mut Self {
self.transcript_commit = Some(transcript_commit);
self
}
/// Builds the config.
pub fn build(self) -> Result<RequestConfig, RequestConfigBuilderError> {
Ok(RequestConfig {
signature_alg: self.signature_alg,
hash_alg: self.hash_alg,
extensions: self.extensions,
transcript_commit: self.transcript_commit,
})
}
}

View File

@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
use crate::{
connection::{ServerCertOpening, ServerIdentityProof, ServerName},
transcript::{encoding::EncodingTree, Transcript, TranscriptProofBuilder},
transcript::{Transcript, TranscriptCommitment, TranscriptProofBuilder, TranscriptSecret},
};
/// Secret data of an [`Attestation`](crate::attestation::Attestation).
@@ -10,8 +10,9 @@ use crate::{
pub struct Secrets {
pub(crate) server_name: ServerName,
pub(crate) server_cert_opening: ServerCertOpening,
pub(crate) encoding_tree: Option<EncodingTree>,
pub(crate) transcript: Transcript,
pub(crate) transcript_commitments: Vec<TranscriptCommitment>,
pub(crate) transcript_commitment_secrets: Vec<TranscriptSecret>,
}
opaque_debug::implement!(Secrets);
@@ -34,6 +35,17 @@ impl Secrets {
/// Returns a transcript proof builder.
pub fn transcript_proof_builder(&self) -> TranscriptProofBuilder<'_> {
TranscriptProofBuilder::new(&self.transcript, self.encoding_tree.as_ref())
let encoding_secret = self
.transcript_commitment_secrets
.iter()
.find_map(|secret| {
#[allow(irrefutable_let_patterns)]
if let TranscriptSecret::Encoding(secret) = secret {
Some(secret)
} else {
None
}
});
TranscriptProofBuilder::new(&self.transcript, encoding_secret)
}
}

View File

@@ -45,7 +45,7 @@ use crate::connection::TranscriptLength;
pub use commit::{
TranscriptCommitConfig, TranscriptCommitConfigBuilder, TranscriptCommitConfigBuilderError,
TranscriptCommitmentKind,
TranscriptCommitRequest, TranscriptCommitment, TranscriptCommitmentKind, TranscriptSecret,
};
pub use proof::{
TranscriptProof, TranscriptProofBuilder, TranscriptProofBuilderError, TranscriptProofError,

View File

@@ -6,8 +6,11 @@ use rangeset::ToRangeSet;
use serde::{Deserialize, Serialize};
use crate::{
hash::HashAlgId,
transcript::{Direction, Idx, Transcript},
hash::{impl_domain_separator, HashAlgId},
transcript::{
encoding::{EncodingCommitment, EncodingTree},
Direction, Idx, Transcript,
},
};
/// The maximum allowed total bytelength of committed data for a single
@@ -41,10 +44,31 @@ impl fmt::Display for TranscriptCommitmentKind {
}
}
/// Transcript commitment.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TranscriptCommitment {
/// Encoding commitment.
Encoding(EncodingCommitment),
}
impl_domain_separator!(TranscriptCommitment);
/// Secret for a transcript commitment.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum TranscriptSecret {
/// Encoding tree.
Encoding(EncodingTree),
}
impl_domain_separator!(TranscriptSecret);
/// Configuration for transcript commitments.
#[derive(Debug, Clone)]
pub struct TranscriptCommitConfig {
encoding_hash_alg: HashAlgId,
has_encoding: bool,
commits: Vec<((Direction, Idx), TranscriptCommitmentKind)>,
}
@@ -61,9 +85,7 @@ impl TranscriptCommitConfig {
/// Returns whether the configuration has any encoding commitments.
pub fn has_encoding(&self) -> bool {
self.commits
.iter()
.any(|(_, kind)| matches!(kind, TranscriptCommitmentKind::Encoding))
self.has_encoding
}
/// Returns an iterator over the encoding commitment indices.
@@ -81,6 +103,13 @@ impl TranscriptCommitConfig {
_ => None,
})
}
/// Returns a request for the transcript commitments.
pub fn to_request(&self) -> TranscriptCommitRequest {
TranscriptCommitRequest {
encoding: self.has_encoding,
}
}
}
/// A builder for [`TranscriptCommitConfig`].
@@ -91,6 +120,7 @@ impl TranscriptCommitConfig {
pub struct TranscriptCommitConfigBuilder<'a> {
transcript: &'a Transcript,
encoding_hash_alg: HashAlgId,
has_encoding: bool,
default_kind: TranscriptCommitmentKind,
commits: HashSet<((Direction, Idx), TranscriptCommitmentKind)>,
}
@@ -101,6 +131,7 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
Self {
transcript,
encoding_hash_alg: HashAlgId::BLAKE3,
has_encoding: false,
default_kind: TranscriptCommitmentKind::Encoding,
commits: HashSet::default(),
}
@@ -145,6 +176,10 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
));
}
if let TranscriptCommitmentKind::Encoding = kind {
self.has_encoding = true;
}
self.commits.insert(((direction, idx), kind));
Ok(self)
@@ -192,6 +227,7 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
pub fn build(self) -> Result<TranscriptCommitConfig, TranscriptCommitConfigBuilderError> {
Ok(TranscriptCommitConfig {
encoding_hash_alg: self.encoding_hash_alg,
has_encoding: self.has_encoding,
commits: Vec::from_iter(self.commits),
})
}
@@ -235,6 +271,19 @@ impl fmt::Display for TranscriptCommitConfigBuilderError {
}
}
/// Request to compute transcript commitments.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TranscriptCommitRequest {
encoding: bool,
}
impl TranscriptCommitRequest {
/// Returns `true` if an encoding commitment is requested.
pub fn encoding(&self) -> bool {
self.encoding
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -11,7 +11,7 @@ mod tree;
pub use encoder::{new_encoder, Encoder, EncoderSecret};
pub use proof::{EncodingProof, EncodingProofError};
pub use provider::{EncodingProvider, EncodingProviderError};
pub use tree::EncodingTree;
pub use tree::{EncodingTree, EncodingTreeError};
use serde::{Deserialize, Serialize};

View File

@@ -231,7 +231,6 @@ mod test {
use tlsn_data_fixtures::http::{request::POST_JSON, response::OK_JSON};
use crate::{
connection::TranscriptLength,
fixtures::{encoder_secret, encoder_secret_tampered_seed, encoding_provider},
hash::Blake3,
transcript::{
@@ -255,17 +254,7 @@ mod test {
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len()));
let provider = encoding_provider(transcript.sent(), transcript.received());
let transcript_length = TranscriptLength {
sent: transcript.sent().len() as u32,
received: transcript.received().len() as u32,
};
let tree = EncodingTree::new(
&Blake3::default(),
[&idx_0, &idx_1],
&provider,
&transcript_length,
)
.unwrap();
let tree = EncodingTree::new(&Blake3::default(), [&idx_0, &idx_1], &provider).unwrap();
let proof = tree.proof([&idx_0, &idx_1].into_iter()).unwrap();

View File

@@ -4,7 +4,6 @@ use bimap::BiMap;
use serde::{Deserialize, Serialize};
use crate::{
connection::TranscriptLength,
hash::{Blinder, HashAlgId, HashAlgorithm, TypedHash},
merkle::MerkleTree,
transcript::{
@@ -67,12 +66,10 @@ impl EncodingTree {
/// * `hasher` - The hash algorithm to use.
/// * `idxs` - The subsequence indices to commit to.
/// * `provider` - The encoding provider.
/// * `transcript_length` - The length of the transcript.
pub fn new<'idx>(
hasher: &dyn HashAlgorithm,
idxs: impl IntoIterator<Item = &'idx (Direction, Idx)>,
provider: &dyn EncodingProvider,
transcript_length: &TranscriptLength,
) -> Result<Self, EncodingTreeError> {
let mut this = Self {
tree: MerkleTree::new(hasher.id()),
@@ -93,18 +90,6 @@ impl EncodingTree {
continue;
}
let len = match direction {
Direction::Sent => transcript_length.sent as usize,
Direction::Received => transcript_length.received as usize,
};
if idx.end() > len {
return Err(EncodingTreeError::OutOfBounds {
index: idx.clone(),
transcript_length: len,
});
}
if this.idxs.contains_right(dir_idx) {
// The subsequence is already in the tree.
continue;
@@ -219,11 +204,8 @@ mod tests {
idxs: impl Iterator<Item = &'seq (Direction, Idx)>,
) -> Result<EncodingTree, EncodingTreeError> {
let provider = encoding_provider(transcript.sent(), transcript.received());
let transcript_length = TranscriptLength {
sent: transcript.sent().len() as u32,
received: transcript.received().len() as u32,
};
EncodingTree::new(&Blake3::default(), idxs, &provider, &transcript_length)
EncodingTree::new(&Blake3::default(), idxs, &provider)
}
#[test]
@@ -328,25 +310,20 @@ mod tests {
let idx_1 = (Direction::Received, Idx::new(0..OK_JSON.len() + 1));
let result = new_tree(&transcript, [&idx_0].into_iter()).unwrap_err();
assert!(matches!(result, EncodingTreeError::OutOfBounds { .. }));
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));
let result = new_tree(&transcript, [&idx_1].into_iter()).unwrap_err();
assert!(matches!(result, EncodingTreeError::OutOfBounds { .. }));
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));
}
#[test]
fn test_encoding_tree_missing_encoding() {
let provider = encoding_provider(&[], &[]);
let transcript_length = TranscriptLength {
sent: 8,
received: 8,
};
let result = EncodingTree::new(
&Blake3::default(),
[(Direction::Sent, Idx::new(0..8))].iter(),
&provider,
&transcript_length,
)
.unwrap_err();
assert!(matches!(result, EncodingTreeError::MissingEncoding { .. }));

View File

@@ -5,9 +5,9 @@ use serde::{Deserialize, Serialize};
use std::{collections::HashSet, fmt};
use crate::{
attestation::Body,
connection::TranscriptLength,
transcript::{
commit::TranscriptCommitmentKind,
commit::{TranscriptCommitment, TranscriptCommitmentKind},
encoding::{EncodingProof, EncodingProofError, EncodingTree},
Direction, Idx, PartialTranscript, Transcript,
},
@@ -36,15 +36,16 @@ impl TranscriptProof {
///
/// * `provider` - The crypto provider to use for verification.
/// * `attestation_body` - The attestation body to verify against.
pub fn verify_with_provider(
pub fn verify_with_provider<'a>(
self,
provider: &CryptoProvider,
attestation_body: &Body,
length: &TranscriptLength,
commitments: impl IntoIterator<Item = &'a TranscriptCommitment>,
) -> Result<PartialTranscript, TranscriptProofError> {
let info = attestation_body.connection_info();
let commitments: Vec<_> = commitments.into_iter().collect();
if self.transcript.sent_unsafe().len() != info.transcript_length.sent as usize
|| self.transcript.received_unsafe().len() != info.transcript_length.received as usize
if self.transcript.sent_unsafe().len() != length.sent as usize
|| self.transcript.received_unsafe().len() != length.received as usize
{
return Err(TranscriptProofError::new(
ErrorKind::Proof,
@@ -57,12 +58,23 @@ impl TranscriptProof {
// Verify encoding proof.
if let Some(proof) = self.encoding_proof {
let commitment = attestation_body.encoding_commitment().ok_or_else(|| {
TranscriptProofError::new(
ErrorKind::Encoding,
"contains an encoding proof but attestation is missing encoding commitment",
)
})?;
let commitment = commitments
.iter()
.find_map(|commitment| {
#[allow(irrefutable_let_patterns)]
if let TranscriptCommitment::Encoding(encoding) = commitment {
Some(encoding)
} else {
None
}
})
.ok_or_else(|| {
TranscriptProofError::new(
ErrorKind::Encoding,
"contains an encoding proof but attestation is missing encoding commitment",
)
})?;
let (auth_sent, auth_recv) = proof.verify_with_provider(
provider,
commitment,
@@ -456,12 +468,8 @@ mod tests {
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
use crate::{
fixtures::{
attestation_fixture, encoder_secret, encoding_provider, request_fixture,
ConnectionFixture, RequestFixture,
},
fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
hash::{Blake3, HashAlgId},
signing::SignatureAlgId,
transcript::TranscriptCommitConfigBuilder,
};
@@ -472,10 +480,7 @@ mod tests {
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
let connection = ConnectionFixture::tlsnotary(transcript.length());
let RequestFixture {
mut request,
encoding_tree,
} = request_fixture(
let RequestFixture { encoding_tree, .. } = request_fixture(
transcript.clone(),
encoding_provider(GET_WITH_HEADER, OK_JSON),
connection.clone(),
@@ -489,19 +494,12 @@ mod tests {
let transcript_proof = builder.build().unwrap();
request.encoding_commitment_root = None;
let attestation = attestation_fixture(
request,
connection,
SignatureAlgId::SECP256K1,
encoder_secret(),
);
let provider = CryptoProvider::default();
let err = transcript_proof
.verify_with_provider(&provider, &attestation.body)
.verify_with_provider(&provider, &transcript.length(), &[])
.err()
.unwrap();
assert!(matches!(err.kind, ErrorKind::Encoding));
}
@@ -620,7 +618,6 @@ mod tests {
&Blake3::default(),
transcripts_commitment_config.iter_encoding(),
&encoding_provider(GET_WITH_HEADER, OK_JSON),
&transcript.length(),
)
.unwrap();
@@ -693,7 +690,6 @@ mod tests {
&Blake3::default(),
transcripts_commitment_config.iter_encoding(),
&encoding_provider(GET_WITH_HEADER, OK_JSON),
&transcript.length(),
)
.unwrap();

View File

@@ -6,7 +6,11 @@ use tlsn_core::{
presentation::PresentationOutput,
request::{Request, RequestConfig},
signing::SignatureAlgId,
transcript::{encoding::EncodingTree, Direction, Transcript, TranscriptCommitConfigBuilder},
transcript::{
encoding::{EncodingCommitment, EncodingTree},
Direction, Transcript, TranscriptCommitConfigBuilder, TranscriptCommitment,
TranscriptSecret,
},
CryptoProvider,
};
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
@@ -54,10 +58,14 @@ fn test_api() {
&Blake3::default(),
transcripts_commitment_config.iter_encoding(),
&encodings_provider,
&transcript.length(),
)
.unwrap();
let encoding_commitment = EncodingCommitment {
root: encoding_tree.root(),
secret: encoder_secret(),
};
let request_config = RequestConfig::default();
let mut request_builder = Request::builder(&request_config);
@@ -65,7 +73,10 @@ fn test_api() {
.server_name(server_name.clone())
.server_cert_data(server_cert_data)
.transcript(transcript)
.encoding_tree(encoding_tree);
.transcript_commitments(
vec![TranscriptSecret::Encoding(encoding_tree)],
vec![TranscriptCommitment::Encoding(encoding_commitment.clone())],
);
let (request, secrets) = request_builder.build(&provider).unwrap();
@@ -84,7 +95,7 @@ fn test_api() {
.connection_info(connection_info.clone())
// Server key Notary received during handshake
.server_ephemeral_key(server_ephemeral_key)
.encoder_secret(encoder_secret());
.transcript_commitments(vec![TranscriptCommitment::Encoding(encoding_commitment)]);
let attestation = attestation_builder.build(&provider).unwrap();

View File

@@ -171,10 +171,7 @@ async fn notarize(
assert!(response.status() == StatusCode::OK);
// The prover task should be done now, so we can await it.
let prover = prover_task.await??;
// Prepare for notarization.
let mut prover = prover.start_notarize();
let mut prover = prover_task.await??;
// Parse the HTTP transcript.
let transcript = HttpTranscript::parse(prover.transcript())?;
@@ -201,10 +198,12 @@ async fn notarize(
// for other strategies that can be used to generate commitments.
DefaultHttpCommitter::default().commit_transcript(&mut builder, &transcript)?;
prover.transcript_commit(builder.build()?);
let transcript_commit = builder.build()?;
// Build an attestation request.
let builder = RequestConfig::builder();
let mut builder = RequestConfig::builder();
builder.transcript_commit(transcript_commit);
// Optionally, add an extension to the attestation if the notary supports it.
// builder.extension(Extension {
@@ -214,7 +213,8 @@ async fn notarize(
let request_config = builder.build()?;
let (attestation, secrets) = prover.finalize(&request_config).await?;
#[allow(deprecated)]
let (attestation, secrets) = prover.notarize(&request_config).await?;
println!("Notarization complete!");

View File

@@ -13,11 +13,13 @@ use tracing::instrument;
use tls_core::verify::WebPkiVerifier;
use tls_server_fixture::CA_CERT_DER;
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
use tlsn_core::{transcript::Idx, CryptoProvider};
use tlsn_prover::{state::Prove, Prover, ProverConfig};
use tlsn_core::{
transcript::PartialTranscript, CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig,
};
use tlsn_prover::{Prover, ProverConfig};
use tlsn_server_fixture::DEFAULT_FIXTURE_PORT;
use tlsn_server_fixture_certs::SERVER_DOMAIN;
use tlsn_verifier::{SessionInfo, Verifier, VerifierConfig};
use tlsn_verifier::{Verifier, VerifierConfig};
const SECRET: &str = "TLSNotary's private key 🤡";
@@ -45,13 +47,16 @@ async fn main() {
let (prover_socket, verifier_socket) = tokio::io::duplex(1 << 23);
let prover = prover(prover_socket, &server_addr, &uri);
let verifier = verifier(verifier_socket);
let (_, (sent, received, _session_info)) = tokio::join!(prover, verifier);
let (_, transcript) = tokio::join!(prover, verifier);
println!("Successfully verified {}", &uri);
println!("Verified sent data:\n{}", bytes_to_redacted_string(&sent));
println!(
"Verified sent data:\n{}",
bytes_to_redacted_string(transcript.sent_unsafe())
);
println!(
"Verified received data:\n{}",
bytes_to_redacted_string(&received)
bytes_to_redacted_string(transcript.received_unsafe())
);
}
@@ -136,21 +141,51 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
assert!(response.status() == StatusCode::OK);
// Create proof for the Verifier.
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
let mut prover = prover_task.await.unwrap().unwrap();
// Reveal parts of the transcript.
let idx_sent = revealed_ranges_sent(&mut prover);
let idx_recv = revealed_ranges_received(&mut prover);
prover.prove_transcript(idx_sent, idx_recv).await.unwrap();
let mut builder = ProveConfig::builder(prover.transcript());
// Finalize.
prover.finalize().await.unwrap()
// Reveal the DNS name.
builder.server_identity();
// Find the secret in the request.
let pos = prover
.transcript()
.sent()
.windows(SECRET.len())
.position(|w| w == SECRET.as_bytes())
.expect("the secret should be in the sent data");
// Reveal everything except for the secret.
builder.reveal_sent(&(0..pos)).unwrap();
builder
.reveal_sent(&(pos + SECRET.len()..prover.transcript().sent().len()))
.unwrap();
// Find the substring "Dick".
let pos = prover
.transcript()
.received()
.windows(4)
.position(|w| w == b"Dick")
.expect("the substring 'Dick' should be in the received data");
// Reveal everything except for the substring.
builder.reveal_recv(&(0..pos)).unwrap();
builder
.reveal_recv(&(pos + 4..prover.transcript().received().len()))
.unwrap();
let config = builder.build().unwrap();
prover.prove(&config).await.unwrap();
prover.close().await.unwrap();
}
#[instrument(skip(socket))]
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
socket: T,
) -> (Vec<u8>, Vec<u8>, SessionInfo) {
) -> PartialTranscript {
// Set up Verifier.
let config_validator = ProtocolConfigValidator::builder()
.max_sent_data(MAX_SENT_DATA)
@@ -179,60 +214,37 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
.unwrap();
let verifier = Verifier::new(verifier_config);
// Verify MPC-TLS and wait for (redacted) data.
let (mut partial_transcript, session_info) = verifier.verify(socket.compat()).await.unwrap();
partial_transcript.set_unauthed(0);
// Receive authenticated data.
let VerifierOutput {
server_name,
transcript,
..
} = verifier
.verify(socket.compat(), &VerifyConfig::default())
.await
.unwrap();
let server_name = server_name.expect("prover should have revealed server name");
let transcript = transcript.expect("prover should have revealed transcript data");
// Check sent data.
let sent = partial_transcript.sent_unsafe().to_vec();
let sent = transcript.sent_unsafe().to_vec();
let sent_data = String::from_utf8(sent.clone()).expect("Verifier expected sent data");
sent_data
.find(SERVER_DOMAIN)
.unwrap_or_else(|| panic!("Verification failed: Expected host {}", SERVER_DOMAIN));
// Check received data.
let received = partial_transcript.received_unsafe().to_vec();
let received = transcript.received_unsafe().to_vec();
let response = String::from_utf8(received.clone()).expect("Verifier expected received data");
response
.find("Herman Melville")
.unwrap_or_else(|| panic!("Expected valid data from {}", SERVER_DOMAIN));
// Check Session info: server name.
assert_eq!(session_info.server_name.as_str(), SERVER_DOMAIN);
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
(sent, received, session_info)
}
/// Returns the received ranges to be revealed to the verifier.
fn revealed_ranges_received(prover: &mut Prover<Prove>) -> Idx {
let recv_transcript = prover.transcript().received();
let recv_transcript_len = recv_transcript.len();
// Get the received data as a string.
let received_string = String::from_utf8(recv_transcript.to_vec()).unwrap();
// Find the substring "illustrative".
let start = received_string
.find("Dick")
.expect("Error: The substring 'Dick' was not found in the received data.");
let end = start + "Dick".len();
Idx::new([0..start, end..recv_transcript_len])
}
/// Returns the sent ranges to be revealed to the verifier.
fn revealed_ranges_sent(prover: &mut Prover<Prove>) -> Idx {
let sent_transcript = prover.transcript().sent();
let sent_transcript_len = sent_transcript.len();
let sent_string = String::from_utf8(sent_transcript.to_vec()).unwrap();
let secret_start = sent_string.find(SECRET).unwrap();
// Reveal everything except for the SECRET.
Idx::new([
0..secret_start,
secret_start + SECRET.len()..sent_transcript_len,
])
transcript
}
/// Render redacted bytes as `🙈`.

View File

@@ -223,6 +223,7 @@ pub async fn notary_service<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
.crypto_provider(crypto_provider)
.build()?;
#[allow(deprecated)]
timeout(
Duration::from_secs(notary_globals.notarization_config.timeout),
Verifier::new(config).notarize(socket.compat(), &att_config),

View File

@@ -250,7 +250,7 @@ async fn test_tcp_prover<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
server_task.await.unwrap().unwrap();
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
let mut prover = prover_task.await.unwrap().unwrap();
let (sent_len, recv_len) = prover.transcript().len();
@@ -259,13 +259,17 @@ async fn test_tcp_prover<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
builder.commit_sent(&(0..sent_len)).unwrap();
builder.commit_recv(&(0..recv_len)).unwrap();
let commit_config = builder.build().unwrap();
let transcript_commit = builder.build().unwrap();
prover.transcript_commit(commit_config);
let mut builder = RequestConfig::builder();
let request = RequestConfig::builder().build().unwrap();
builder.transcript_commit(transcript_commit);
_ = prover.finalize(&request).await.unwrap();
let request = builder.build().unwrap();
#[allow(deprecated)]
prover.notarize(&request).await.unwrap();
prover.close().await.unwrap();
debug!("Done notarization!");
}
@@ -439,7 +443,7 @@ async fn test_websocket_prover() {
server_task.await.unwrap().unwrap();
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
let mut prover = prover_task.await.unwrap().unwrap();
let (sent_len, recv_len) = prover.transcript().len();
@@ -448,14 +452,17 @@ async fn test_websocket_prover() {
builder.commit_sent(&(0..sent_len)).unwrap();
builder.commit_recv(&(0..recv_len)).unwrap();
let commit_config = builder.build().unwrap();
let transcript_commit = builder.build().unwrap();
prover.transcript_commit(commit_config);
let mut builder = RequestConfig::builder();
let request = RequestConfig::builder().build().unwrap();
builder.transcript_commit(transcript_commit);
_ = prover.finalize(&request).await.unwrap();
let request = builder.build().unwrap();
#[allow(deprecated)]
prover.notarize(&request).await.unwrap();
prover.close().await.unwrap();
debug!("Done notarization!");
}

View File

@@ -7,8 +7,9 @@ use std::pin::Pin;
/// Prover future which must be polled for the TLS connection to make progress.
pub struct ProverFuture {
#[allow(clippy::type_complexity)]
pub(crate) fut:
Pin<Box<dyn Future<Output = Result<Prover<state::Closed>, ProverError>> + Send + 'static>>,
pub(crate) fut: Pin<
Box<dyn Future<Output = Result<Prover<state::Committed>, ProverError>> + Send + 'static>,
>,
pub(crate) ctrl: ProverControl,
}
@@ -20,7 +21,7 @@ impl ProverFuture {
}
impl Future for ProverFuture {
type Output = Result<Prover<state::Closed>, ProverError>;
type Output = Result<Prover<state::Committed>, ProverError>;
fn poll(
mut self: Pin<&mut Self>,

View File

@@ -7,22 +7,22 @@
mod config;
mod error;
mod future;
mod notarize;
mod prove;
pub mod state;
pub use config::{ProverConfig, ProverConfigBuilder, ProverConfigBuilderError};
pub use error::ProverError;
pub use future::ProverFuture;
pub use tlsn_core::{ProveConfig, ProveConfigBuilder, ProveConfigBuilderError, ProverOutput};
use mpz_common::Context;
use mpz_core::Block;
use mpz_garble_core::Delta;
use state::{Notarize, Prove};
use mpz_vm_core::prelude::*;
use futures::{AsyncRead, AsyncWrite, TryFutureExt};
use mpc_tls::{LeaderCtrl, MpcTlsLeader, SessionKeys};
use rand::Rng;
use serio::SinkExt;
use serio::{stream::IoStreamExt, SinkExt};
use std::sync::Arc;
use tls_client::{ClientConnection, ServerName as TlsServerName};
use tls_client_async::{bind_client, TlsConnection};
@@ -30,17 +30,21 @@ use tls_core::msgs::enums::ContentType;
use tlsn_common::{
commit::commit_records,
context::build_mt_context,
encoding,
mux::attach_mux,
transcript::{Record, TlsTranscript},
transcript::{decode_transcript, Record, TlsTranscript},
zk_aes::ZkAesCtr,
Role,
};
use tlsn_core::{
attestation::Attestation,
connection::{
ConnectionInfo, HandshakeData, HandshakeDataV1_2, ServerCertData, ServerSignature,
TranscriptLength,
},
transcript::Transcript,
request::{Request, RequestConfig},
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
ProvePayload, Secrets,
};
use tlsn_deap::Deap;
use tokio::sync::Mutex;
@@ -62,7 +66,7 @@ pub(crate) type Zk = mpz_zk::Prover<RCOTReceiver>;
/// A prover instance.
#[derive(Debug)]
pub struct Prover<T: state::ProverState> {
pub struct Prover<T: state::ProverState = state::Initialized> {
config: ProverConfig,
span: Span,
state: T,
@@ -131,7 +135,6 @@ impl Prover<state::Initialized> {
state: state::Setup {
mux_ctrl,
mux_fut,
mt,
mpc_tls,
zk_aes,
keys,
@@ -158,11 +161,11 @@ impl Prover<state::Setup> {
let state::Setup {
mux_ctrl,
mut mux_fut,
mt,
mpc_tls,
mut zk_aes,
keys,
vm,
..
} = self.state;
let (mpc_ctrl, mpc_fut) = mpc_tls.run();
@@ -292,10 +295,9 @@ impl Prover<state::Setup> {
Ok(Prover {
config: self.config,
span: self.span,
state: state::Closed {
state: state::Committed {
mux_ctrl,
mux_fut,
mt,
ctx,
_keys: keys,
vm,
@@ -319,35 +321,182 @@ impl Prover<state::Setup> {
}
}
impl Prover<state::Closed> {
impl Prover<state::Committed> {
/// Returns the connection information.
pub fn connection_info(&self) -> &ConnectionInfo {
&self.state.connection_info
}
/// Returns the transcript.
pub fn transcript(&self) -> &Transcript {
&self.state.transcript
}
/// Starts notarization of the TLS session.
/// Proves information to the verifier.
///
/// Used when the TLS verifier is a Notary to transition the prover to the
/// next state where it can generate commitments to the transcript prior
/// to finalization.
pub fn start_notarize(self) -> Prover<Notarize> {
Prover {
config: self.config,
span: self.span,
state: self.state.into(),
/// # Arguments
///
/// * `config` - The disclosure configuration.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn prove(&mut self, config: &ProveConfig) -> Result<ProverOutput, ProverError> {
let state::Committed {
mux_fut,
ctx,
vm,
server_cert_data,
transcript_refs,
..
} = &mut self.state;
let mut output = ProverOutput {
transcript_commitments: Vec::new(),
transcript_secrets: Vec::new(),
};
let payload = ProvePayload {
server_identity: config
.server_identity()
.then(|| (self.config.server_name().clone(), server_cert_data.clone())),
transcript: config.transcript().cloned(),
transcript_commit: config.transcript_commit().map(|config| config.to_request()),
};
// Send payload.
mux_fut
.poll_with(ctx.io_mut().send(payload).map_err(ProverError::from))
.await?;
if let Some(partial_transcript) = config.transcript() {
decode_transcript(
vm,
partial_transcript.sent_authed(),
partial_transcript.received_authed(),
transcript_refs,
)
.map_err(ProverError::zk)?;
}
if let Some(commit_config) = config.transcript_commit() {
if commit_config.has_encoding() {
let hasher = self
.config
.crypto_provider()
.hash
.get(commit_config.encoding_hash_alg())
.map_err(ProverError::config)?;
let (commitment, tree) = mux_fut
.poll_with(
encoding::receive(
ctx,
hasher,
transcript_refs,
|plaintext| vm.get_macs(plaintext).expect("reference is valid"),
commit_config.iter_encoding(),
)
.map_err(ProverError::commit),
)
.await?;
output
.transcript_commitments
.push(TranscriptCommitment::Encoding(commitment));
output
.transcript_secrets
.push(TranscriptSecret::Encoding(tree));
}
// TODO: Other commitment types.
}
mux_fut
.poll_with(vm.execute_all(ctx).map_err(ProverError::zk))
.await?;
Ok(output)
}
/// Starts proving the TLS session.
/// Requests an attestation from the verifier.
///
/// This function transitions the prover into a state where it can prove
/// content of the transcript.
pub fn start_prove(self) -> Prover<Prove> {
Prover {
config: self.config,
span: self.span,
state: self.state.into(),
/// # Arguments
///
/// * `config` - The attestation request configuration.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
#[deprecated(
note = "attestation functionality will be removed from this API in future releases."
)]
pub async fn notarize(
&mut self,
config: &RequestConfig,
) -> Result<(Attestation, Secrets), ProverError> {
let mut builder = ProveConfig::builder(self.transcript());
if let Some(config) = config.transcript_commit() {
builder.transcript_commit(config.clone());
}
let disclosure_config = builder.build().map_err(ProverError::attestation)?;
let ProverOutput {
transcript_commitments,
transcript_secrets,
..
} = self.prove(&disclosure_config).await?;
let state::Committed {
mux_fut,
ctx,
server_cert_data,
transcript,
..
} = &mut self.state;
let mut builder = Request::builder(config);
builder
.server_name(self.config.server_name().clone())
.server_cert_data(server_cert_data.clone())
.transcript(transcript.clone())
.transcript_commitments(transcript_secrets, transcript_commitments);
let (request, secrets) = builder
.build(self.config.crypto_provider())
.map_err(ProverError::attestation)?;
let attestation = mux_fut
.poll_with(async {
debug!("sending attestation request");
ctx.io_mut().send(request.clone()).await?;
let attestation: Attestation = ctx.io_mut().expect_next().await?;
Ok::<_, ProverError>(attestation)
})
.await?;
// Check the attestation is consistent with the Prover's view.
request
.validate(&attestation)
.map_err(ProverError::attestation)?;
Ok((attestation, secrets))
}
/// Closes the connection with the verifier.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn close(self) -> Result<(), ProverError> {
let state::Committed {
mux_ctrl, mux_fut, ..
} = self.state;
// Wait for the verifier to correctly close the connection.
if !mux_fut.is_complete() {
mux_ctrl.close();
mux_fut.await?;
}
Ok(())
}
}

View File

@@ -1,117 +0,0 @@
//! This module handles the notarization phase of the prover.
//!
//! The prover interacts with a TLS verifier who acts as a Notary, i.e. the
//! verifier produces an attestation but does not verify transcript data.
use super::{state::Notarize, Prover, ProverError};
use serio::{stream::IoStreamExt as _, SinkExt as _};
use tlsn_common::encoding;
use tlsn_core::{
attestation::Attestation,
request::{Request, RequestConfig},
transcript::{encoding::EncodingTree, Transcript, TranscriptCommitConfig},
Secrets,
};
use tracing::{debug, instrument};
impl Prover<Notarize> {
/// Returns the transcript.
pub fn transcript(&self) -> &Transcript {
&self.state.transcript
}
/// Configures transcript commitments.
pub fn transcript_commit(&mut self, config: TranscriptCommitConfig) {
self.state.transcript_commit_config = Some(config);
}
/// Finalizes the notarization.
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
pub async fn finalize(
self,
config: &RequestConfig,
) -> Result<(Attestation, Secrets), ProverError> {
let Notarize {
mux_ctrl,
mut mux_fut,
mut ctx,
vm,
connection_info,
server_cert_data,
transcript,
transcript_refs,
transcript_commit_config,
..
} = self.state;
let sent_macs = transcript_refs
.sent()
.iter()
.flat_map(|plaintext| vm.get_macs(*plaintext).expect("reference is valid"))
.map(|mac| mac.as_block());
let recv_macs = transcript_refs
.recv()
.iter()
.flat_map(|plaintext| vm.get_macs(*plaintext).expect("reference is valid"))
.map(|mac| mac.as_block());
let encoding_provider = mux_fut
.poll_with(encoding::receive(&mut ctx, sent_macs, recv_macs))
.await?;
let provider = self.config.crypto_provider();
let hasher = provider
.hash
.get(config.hash_alg())
.map_err(ProverError::config)?;
let mut builder = Request::builder(config);
builder
.server_name(self.config.server_name().clone())
.server_cert_data(server_cert_data)
.transcript(transcript);
if let Some(config) = transcript_commit_config {
if config.has_encoding() {
builder.encoding_tree(
EncodingTree::new(
hasher,
config.iter_encoding(),
&encoding_provider,
&connection_info.transcript_length,
)
.map_err(ProverError::commit)?,
);
}
}
let (request, secrets) = builder.build(provider).map_err(ProverError::attestation)?;
let attestation = mux_fut
.poll_with(async {
debug!("sending attestation request");
ctx.io_mut().send(request.clone()).await?;
let attestation: Attestation = ctx.io_mut().expect_next().await?;
Ok::<_, ProverError>(attestation)
})
.await?;
// Wait for the notary to correctly close the connection.
if !mux_fut.is_complete() {
mux_ctrl.close();
mux_fut.await?;
}
// Check the attestation is consistent with the Prover's view.
request
.validate(&attestation)
.map_err(ProverError::attestation)?;
Ok((attestation, secrets))
}
}

View File

@@ -1,104 +0,0 @@
//! This module handles the proving phase of the prover.
//!
//! The prover interacts with a TLS verifier directly, without involving a
//! Notary. The verifier verifies transcript data.
use mpz_memory_core::MemoryExt;
use mpz_vm_core::Execute;
use serio::SinkExt as _;
use tlsn_common::msg::ServerIdentityProof;
use tlsn_core::transcript::{Direction, Idx, Transcript};
use tracing::{info, instrument};
use crate::{state::Prove as ProveState, Prover, ProverError};
impl Prover<ProveState> {
/// Returns the transcript.
pub fn transcript(&self) -> &Transcript {
&self.state.transcript
}
/// Proves subsequences in the transcript to the verifier.
///
/// # Arguments
///
/// * `sent` - Indices of the sent data.
/// * `recv` - Indices of the received data.
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
pub async fn prove_transcript(&mut self, sent: Idx, recv: Idx) -> Result<(), ProverError> {
let partial_transcript = self.transcript().to_partial(sent.clone(), recv.clone());
let sent_refs = self
.state
.transcript_refs
.get(Direction::Sent, &sent)
.expect("index is in bounds");
let recv_refs = self
.state
.transcript_refs
.get(Direction::Received, &recv)
.expect("index is in bounds");
for slice in sent_refs.into_iter().chain(recv_refs) {
// Drop the future, we don't need it.
drop(self.state.vm.decode(slice).map_err(ProverError::zk)?);
}
self.state
.mux_fut
.poll_with(async {
// Send the partial transcript to the verifier.
self.state.ctx.io_mut().send(partial_transcript).await?;
info!("Sent partial transcript");
// Prove the partial transcript to the verifier.
self.state
.vm
.flush(&mut self.state.ctx)
.await
.map_err(ProverError::zk)?;
info!("Proved partial transcript");
Ok::<_, ProverError>(())
})
.await?;
Ok(())
}
/// Finalizes the proving.
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
pub async fn finalize(self) -> Result<(), ProverError> {
let ProveState {
mux_ctrl,
mut mux_fut,
mut ctx,
server_cert_data,
..
} = self.state;
mux_fut
.poll_with(async move {
// Send identity proof to the verifier.
ctx.io_mut()
.send(ServerIdentityProof {
name: self.config.server_name().clone(),
data: server_cert_data,
})
.await?;
Ok::<_, ProverError>(())
})
.await?;
// Wait for the verifier to correctly close the connection.
if !mux_fut.is_complete() {
mux_ctrl.close();
mux_fut.await?;
}
Ok(())
}
}

View File

@@ -2,7 +2,7 @@
use std::sync::Arc;
use mpz_common::{context::Multithread, Context};
use mpz_common::Context;
use mpc_tls::{MpcTlsLeader, SessionKeys};
use tlsn_common::{
@@ -12,7 +12,7 @@ use tlsn_common::{
};
use tlsn_core::{
connection::{ConnectionInfo, ServerCertData},
transcript::{Transcript, TranscriptCommitConfig},
transcript::Transcript,
};
use tlsn_deap::Deap;
use tokio::sync::Mutex;
@@ -28,7 +28,6 @@ opaque_debug::implement!(Initialized);
pub struct Setup {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) mt: Multithread,
pub(crate) mpc_tls: MpcTlsLeader,
pub(crate) zk_aes: ZkAesCtr,
pub(crate) keys: SessionKeys,
@@ -37,11 +36,10 @@ pub struct Setup {
opaque_debug::implement!(Setup);
/// State after the TLS connection has been closed.
pub struct Closed {
/// State after the TLS connection has been committed and closed.
pub struct Committed {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) mt: Multithread,
pub(crate) ctx: Context,
pub(crate) _keys: SessionKeys,
pub(crate) vm: Zk,
@@ -51,84 +49,18 @@ pub struct Closed {
pub(crate) transcript_refs: TranscriptRefs,
}
opaque_debug::implement!(Closed);
/// Notarizing state.
pub struct Notarize {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) _mt: Multithread,
pub(crate) ctx: Context,
pub(crate) vm: Zk,
pub(crate) connection_info: ConnectionInfo,
pub(crate) server_cert_data: ServerCertData,
pub(crate) transcript: Transcript,
pub(crate) transcript_refs: TranscriptRefs,
pub(crate) transcript_commit_config: Option<TranscriptCommitConfig>,
}
opaque_debug::implement!(Notarize);
impl From<Closed> for Notarize {
fn from(state: Closed) -> Self {
Self {
mux_ctrl: state.mux_ctrl,
mux_fut: state.mux_fut,
_mt: state.mt,
ctx: state.ctx,
vm: state.vm,
connection_info: state.connection_info,
server_cert_data: state.server_cert_data,
transcript: state.transcript,
transcript_refs: state.transcript_refs,
transcript_commit_config: None,
}
}
}
/// Proving state.
pub struct Prove {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) _mt: Multithread,
pub(crate) ctx: Context,
pub(crate) vm: Zk,
pub(crate) _connection_info: ConnectionInfo,
pub(crate) server_cert_data: ServerCertData,
pub(crate) transcript: Transcript,
pub(crate) transcript_refs: TranscriptRefs,
}
impl From<Closed> for Prove {
fn from(state: Closed) -> Self {
Self {
mux_ctrl: state.mux_ctrl,
mux_fut: state.mux_fut,
_mt: state.mt,
ctx: state.ctx,
vm: state.vm,
_connection_info: state.connection_info,
server_cert_data: state.server_cert_data,
transcript: state.transcript,
transcript_refs: state.transcript_refs,
}
}
}
opaque_debug::implement!(Committed);
#[allow(missing_docs)]
pub trait ProverState: sealed::Sealed {}
impl ProverState for Initialized {}
impl ProverState for Setup {}
impl ProverState for Closed {}
impl ProverState for Notarize {}
impl ProverState for Prove {}
impl ProverState for Committed {}
mod sealed {
pub trait Sealed {}
impl Sealed for super::Initialized {}
impl Sealed for super::Setup {}
impl Sealed for super::Closed {}
impl Sealed for super::Notarize {}
impl Sealed for super::Prove {}
impl Sealed for super::Committed {}
}

View File

@@ -34,6 +34,7 @@ async fn test_defer_decryption() {
}
#[instrument(skip(notary_socket))]
#[allow(deprecated)]
async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socket: T) {
let (client_socket, server_socket) = tokio::io::duplex(2 << 16);
@@ -83,7 +84,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
let _ = server_task.await.unwrap();
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
let mut prover = prover_task.await.unwrap().unwrap();
let sent_tx_len = prover.transcript().sent().len();
let recv_tx_len = prover.transcript().received().len();
@@ -93,16 +94,20 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
builder.commit_sent(&(0..sent_tx_len)).unwrap();
builder.commit_recv(&(0..recv_tx_len)).unwrap();
let transcript_commit = builder.build().unwrap();
let mut builder = RequestConfig::builder();
builder.transcript_commit(transcript_commit);
let config = builder.build().unwrap();
prover.transcript_commit(config);
let config = RequestConfig::default();
prover.finalize(&config).await.unwrap();
prover.notarize(&config).await.unwrap();
prover.close().await.unwrap();
}
#[instrument(skip(socket))]
#[allow(deprecated)]
async fn notary<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(socket: T) {
let mut root_store = tls_core::anchors::RootCertStore::empty();
root_store

View File

@@ -35,6 +35,7 @@ async fn notarize() {
}
#[instrument(skip(notary_socket))]
#[allow(deprecated)]
async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socket: T) {
let (client_socket, server_socket) = tokio::io::duplex(2 << 16);
@@ -98,7 +99,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
let _ = server_task.await.unwrap();
let mut prover = prover_task.await.unwrap().unwrap().start_notarize();
let mut prover = prover_task.await.unwrap().unwrap();
let sent_tx_len = prover.transcript().sent().len();
let recv_tx_len = prover.transcript().received().len();
@@ -108,12 +109,11 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
builder.commit_sent(&(0..sent_tx_len)).unwrap();
builder.commit_recv(&(0..recv_tx_len)).unwrap();
let config = builder.build().unwrap();
prover.transcript_commit(config);
let transcript_commit = builder.build().unwrap();
let mut builder = RequestConfig::builder();
builder.transcript_commit(transcript_commit);
builder.extension(Extension {
id: b"foo".to_vec(),
value: b"bar".to_vec(),
@@ -121,12 +121,14 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
let config = builder.build().unwrap();
let (attestation, _) = prover.finalize(&config).await.unwrap();
let (attestation, _) = prover.notarize(&config).await.unwrap();
prover.close().await.unwrap();
assert_eq!(attestation.body.extensions().count(), 1);
}
#[instrument(skip(socket))]
#[allow(deprecated)]
async fn notary<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(socket: T) {
let mut root_store = tls_core::anchors::RootCertStore::empty();
root_store

View File

@@ -1,13 +1,10 @@
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
use tlsn_core::{
transcript::{Idx, PartialTranscript},
CryptoProvider,
};
use tlsn_core::{transcript::Idx, CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig};
use tlsn_prover::{Prover, ProverConfig};
use tlsn_server_fixture::bind;
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
use tlsn_verifier::{SessionInfo, Verifier, VerifierConfig};
use tlsn_verifier::{Verifier, VerifierConfig};
use http_body_util::{BodyExt as _, Empty};
use hyper::{body::Bytes, Request, StatusCode};
@@ -29,17 +26,27 @@ async fn verify() {
let (socket_0, socket_1) = tokio::io::duplex(1 << 23);
let (_, (partial_transcript, info)) = tokio::join!(prover(socket_0), verifier(socket_1));
let (
_,
VerifierOutput {
server_name,
transcript,
..
},
) = tokio::join!(prover(socket_0), verifier(socket_1));
let server_name = server_name.unwrap();
let transcript = transcript.unwrap();
assert_eq!(
partial_transcript.sent_authed(),
&Idx::new(0..partial_transcript.len_sent() - 1)
transcript.sent_authed(),
&Idx::new(0..transcript.len_sent() - 1)
);
assert_eq!(
partial_transcript.received_authed(),
&Idx::new(2..partial_transcript.len_received())
transcript.received_authed(),
&Idx::new(2..transcript.len_received())
);
assert_eq!(info.server_name.as_str(), SERVER_DOMAIN);
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
}
#[instrument(skip(notary_socket))]
@@ -105,22 +112,29 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
let _ = server_task.await.unwrap();
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
let mut prover = prover_task.await.unwrap().unwrap();
let (sent_len, recv_len) = prover.transcript().len();
let idx_sent = Idx::new(0..sent_len - 1);
let idx_recv = Idx::new(2..recv_len);
let mut builder = ProveConfig::builder(prover.transcript());
// Reveal parts of the transcript
prover.prove_transcript(idx_sent, idx_recv).await.unwrap();
prover.finalize().await.unwrap();
builder
.server_identity()
.reveal_sent(&(0..sent_len - 1))
.unwrap()
.reveal_recv(&(2..recv_len))
.unwrap();
let config = builder.build().unwrap();
prover.prove(&config).await.unwrap();
prover.close().await.unwrap();
}
#[instrument(skip(socket))]
async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
socket: T,
) -> (PartialTranscript, SessionInfo) {
) -> VerifierOutput {
let mut root_store = RootCertStore::empty();
root_store
.add(&tls_core::key::Certificate(CA_CERT_DER.to_vec()))
@@ -145,5 +159,8 @@ async fn verifier<T: AsyncWrite + AsyncRead + Send + Sync + Unpin + 'static>(
let verifier = Verifier::new(config);
verifier.verify(socket.compat()).await.unwrap()
verifier
.verify(socket.compat(), &VerifyConfig::default())
.await
.unwrap()
}

View File

@@ -6,36 +6,38 @@
pub(crate) mod config;
mod error;
mod notarize;
pub mod state;
mod verify;
use std::sync::Arc;
pub use config::{VerifierConfig, VerifierConfigBuilder, VerifierConfigBuilderError};
pub use error::VerifierError;
pub use tlsn_core::{VerifierOutput, VerifyConfig, VerifyConfigBuilder, VerifyConfigBuilderError};
use futures::{AsyncRead, AsyncWrite};
use futures::{AsyncRead, AsyncWrite, TryFutureExt};
use mpc_tls::{FollowerData, MpcTlsFollower, SessionKeys};
use mpz_common::Context;
use mpz_core::Block;
use mpz_garble_core::Delta;
use serio::stream::IoStreamExt;
use state::{Notarize, Verify};
use mpz_vm_core::prelude::*;
use serio::{stream::IoStreamExt, SinkExt};
use tls_core::msgs::enums::ContentType;
use tlsn_common::{
commit::commit_records,
config::ProtocolConfig,
context::build_mt_context,
encoding,
mux::attach_mux,
transcript::{Record, TlsTranscript},
transcript::{decode_transcript, verify_transcript, Record, TlsTranscript},
zk_aes::ZkAesCtr,
Role,
};
use tlsn_core::{
attestation::{Attestation, AttestationConfig},
connection::{ConnectionInfo, ServerName, TlsVersion, TranscriptLength},
transcript::PartialTranscript,
request::Request,
transcript::TranscriptCommitment,
ProvePayload,
};
use tlsn_deap::Deap;
use tokio::sync::Mutex;
@@ -66,7 +68,7 @@ pub struct SessionInfo {
}
/// A Verifier instance.
pub struct Verifier<T: state::VerifierState> {
pub struct Verifier<T: state::VerifierState = state::Initialized> {
config: VerifierConfig,
span: Span,
state: T,
@@ -138,7 +140,6 @@ impl Verifier<state::Initialized> {
state: state::Setup {
mux_ctrl,
mux_fut,
mt,
delta,
mpc_tls,
zk_aes,
@@ -148,7 +149,7 @@ impl Verifier<state::Initialized> {
})
}
/// Runs the TLS verifier to completion, notarizing the TLS session.
/// Runs the verifier to completion and attests to the TLS session.
///
/// This is a convenience method which runs all the steps needed for
/// notarization.
@@ -158,18 +159,22 @@ impl Verifier<state::Initialized> {
/// * `socket` - The socket to the prover.
/// * `config` - The attestation configuration.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
#[deprecated(
note = "attestation functionality will be removed from this API in future releases."
)]
pub async fn notarize<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
self,
socket: S,
config: &AttestationConfig,
) -> Result<Attestation, VerifierError> {
self.setup(socket)
.await?
.run()
.await?
.start_notarize()
.finalize(config)
.await
let mut verifier = self.setup(socket).await?.run().await?;
#[allow(deprecated)]
let attestation = verifier.notarize(config).await?;
verifier.close().await?;
Ok(attestation)
}
/// Runs the TLS verifier to completion, verifying the TLS session.
@@ -184,23 +189,25 @@ impl Verifier<state::Initialized> {
pub async fn verify<S: AsyncWrite + AsyncRead + Send + Unpin + 'static>(
self,
socket: S,
) -> Result<(PartialTranscript, SessionInfo), VerifierError> {
let mut verifier = self.setup(socket).await?.run().await?.start_verify();
let transcript = verifier.receive().await?;
config: &VerifyConfig,
) -> Result<VerifierOutput, VerifierError> {
let mut verifier = self.setup(socket).await?.run().await?;
let session_info = verifier.finalize().await?;
Ok((transcript, session_info))
let output = verifier.verify(config).await?;
verifier.close().await?;
Ok(output)
}
}
impl Verifier<state::Setup> {
/// Runs the verifier until the TLS connection is closed.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn run(self) -> Result<Verifier<state::Closed>, VerifierError> {
pub async fn run(self) -> Result<Verifier<state::Committed>, VerifierError> {
let state::Setup {
mux_ctrl,
mut mux_fut,
mt,
delta,
mpc_tls,
mut zk_aes,
@@ -220,7 +227,7 @@ impl Verifier<state::Setup> {
FollowerData {
server_key,
mut transcript,
keys,
..
},
) = mux_fut.poll_with(mpc_tls.run()).await?;
@@ -288,13 +295,11 @@ impl Verifier<state::Setup> {
Ok(Verifier {
config: self.config,
span: self.span,
state: state::Closed {
state: state::Committed {
mux_ctrl,
mux_fut,
mt,
delta,
ctx,
keys,
vm,
server_ephemeral_key: server_key
.try_into()
@@ -306,30 +311,192 @@ impl Verifier<state::Setup> {
}
}
impl Verifier<state::Closed> {
/// Starts notarization of the TLS session.
///
/// If the verifier is a Notary, this function will transition the verifier
/// to the next state where it can sign the prover's commitments to the
/// transcript.
pub fn start_notarize(self) -> Verifier<Notarize> {
Verifier {
config: self.config,
span: self.span,
state: self.state.into(),
}
impl Verifier<state::Committed> {
/// Returns the connection information.
pub fn connection_info(&self) -> &ConnectionInfo {
&self.state.connection_info
}
/// Starts verification of the TLS session.
/// Verifies information from the prover.
///
/// This function transitions the verifier into a state where it can verify
/// the contents of the transcript.
pub fn start_verify(self) -> Verifier<Verify> {
Verifier {
config: self.config,
span: self.span,
state: self.state.into(),
/// # Arguments
///
/// * `config` - Verification configuration.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn verify(
&mut self,
#[allow(unused_variables)] config: &VerifyConfig,
) -> Result<VerifierOutput, VerifierError> {
let state::Committed {
mux_fut,
ctx,
delta,
vm,
connection_info,
server_ephemeral_key,
transcript_refs,
..
} = &mut self.state;
let ProvePayload {
server_identity,
transcript,
transcript_commit,
} = mux_fut
.poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from))
.await?;
let server_name = if let Some((name, cert_data)) = server_identity {
cert_data
.verify_with_provider(
self.config.crypto_provider(),
connection_info.time,
server_ephemeral_key,
&name,
)
.map_err(VerifierError::verify)?;
Some(name)
} else {
None
};
if let Some(partial_transcript) = &transcript {
// Check ranges.
if partial_transcript.len_sent() != connection_info.transcript_length.sent as usize
|| partial_transcript.len_received()
!= connection_info.transcript_length.received as usize
{
return Err(VerifierError::verify(
"prover sent transcript with incorrect length",
));
}
decode_transcript(
vm,
partial_transcript.sent_authed(),
partial_transcript.received_authed(),
transcript_refs,
)
.map_err(VerifierError::zk)?;
}
let mut transcript_commitments = Vec::new();
if let Some(commit_config) = transcript_commit {
if commit_config.encoding() {
let commitment = mux_fut
.poll_with(encoding::transfer(
ctx,
transcript_refs,
delta,
|plaintext| vm.get_keys(plaintext).expect("reference is valid"),
))
.await?;
transcript_commitments.push(TranscriptCommitment::Encoding(commitment));
}
// TODO: Other commitment types.
}
mux_fut
.poll_with(vm.execute_all(ctx).map_err(VerifierError::zk))
.await?;
// Verify revealed data.
if let Some(partial_transcript) = &transcript {
verify_transcript(vm, partial_transcript, transcript_refs)
.map_err(VerifierError::verify)?;
}
Ok(VerifierOutput {
server_name,
transcript,
transcript_commitments,
})
}
/// Attests to the TLS session.
///
/// # Arguments
///
/// * `config` - Attestation configuration.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
#[deprecated(
note = "attestation functionality will be removed from this API in future releases."
)]
pub async fn notarize(
&mut self,
config: &AttestationConfig,
) -> Result<Attestation, VerifierError> {
let VerifierOutput {
server_name,
transcript,
transcript_commitments,
} = self.verify(&VerifyConfig::default()).await?;
if server_name.is_some() {
return Err(VerifierError::attestation(
"server name can not be revealed to a notary",
));
} else if transcript.is_some() {
return Err(VerifierError::attestation(
"transcript data can not be revealed to a notary",
));
}
let state::Committed {
mux_fut,
ctx,
server_ephemeral_key,
connection_info,
..
} = &mut self.state;
let request: Request = mux_fut
.poll_with(ctx.io_mut().expect_next().map_err(VerifierError::from))
.await?;
let mut builder = Attestation::builder(config)
.accept_request(request)
.map_err(VerifierError::attestation)?;
builder
.connection_info(connection_info.clone())
.server_ephemeral_key(server_ephemeral_key.clone())
.transcript_commitments(transcript_commitments);
let attestation = builder
.build(self.config.crypto_provider())
.map_err(VerifierError::attestation)?;
mux_fut
.poll_with(
ctx.io_mut()
.send(attestation.clone())
.map_err(VerifierError::from),
)
.await?;
info!("Sent attestation");
Ok(attestation)
}
/// Closes the connection with the prover.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn close(self) -> Result<(), VerifierError> {
let state::Committed {
mux_ctrl, mux_fut, ..
} = self.state;
// Wait for the prover to correctly close the connection.
if !mux_fut.is_complete() {
mux_ctrl.close();
mux_fut.await?;
}
Ok(())
}
}

View File

@@ -1,88 +0,0 @@
//! This module handles the notarization phase of the verifier.
//!
//! The TLS verifier acts as a Notary, i.e. the verifier produces an
//! attestation but does not verify transcript data.
use super::{state::Notarize, Verifier, VerifierError};
use rand::Rng;
use serio::{stream::IoStreamExt, SinkExt as _};
use tlsn_common::encoding;
use tlsn_core::{
attestation::{Attestation, AttestationConfig},
request::Request,
transcript::encoding::EncoderSecret,
};
use tracing::{info, instrument};
impl Verifier<Notarize> {
/// Notarizes the TLS session.
///
/// # Arguments
///
/// * `config` - The attestation configuration.
#[instrument(parent = &self.span, level = "debug", skip_all, err)]
pub async fn finalize(self, config: &AttestationConfig) -> Result<Attestation, VerifierError> {
let Notarize {
mux_ctrl,
mut mux_fut,
delta,
mut ctx,
vm,
server_ephemeral_key,
connection_info,
transcript_refs,
..
} = self.state;
let encoder_secret = EncoderSecret::new(rand::rng().random(), delta.as_block().to_bytes());
let attestation = mux_fut
.poll_with(async {
let sent_keys = transcript_refs
.sent()
.iter()
.flat_map(|plaintext| vm.get_keys(*plaintext).expect("reference is valid"))
.map(|key| key.as_block());
let recv_keys = transcript_refs
.recv()
.iter()
.flat_map(|plaintext| vm.get_keys(*plaintext).expect("reference is valid"))
.map(|key| key.as_block());
// Convert encodings into a structured format.
encoding::transfer(&mut ctx, &encoder_secret, sent_keys, recv_keys).await?;
// Receive attestation request, which also contains commitments required before
// finalization.
let request: Request = ctx.io_mut().expect_next().await?;
let mut builder = Attestation::builder(config)
.accept_request(request)
.map_err(VerifierError::attestation)?;
builder
.connection_info(connection_info)
.server_ephemeral_key(server_ephemeral_key)
.encoder_secret(encoder_secret);
let attestation = builder
.build(self.config.crypto_provider())
.map_err(VerifierError::attestation)?;
ctx.io_mut().send(attestation.clone()).await?;
info!("Sent attestation");
Ok::<_, VerifierError>(attestation)
})
.await?;
if !mux_fut.is_complete() {
mux_ctrl.close();
mux_fut.await?;
}
Ok(attestation)
}
}

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
use crate::{Mpc, Zk};
use mpc_tls::{MpcTlsFollower, SessionKeys};
use mpz_common::{context::Multithread, Context};
use mpz_common::Context;
use mpz_memory_core::correlated::Delta;
use tlsn_common::{
mux::{MuxControl, MuxFuture},
@@ -27,7 +27,6 @@ opaque_debug::implement!(Initialized);
pub struct Setup {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) mt: Multithread,
pub(crate) delta: Delta,
pub(crate) mpc_tls: MpcTlsFollower,
pub(crate) zk_aes: ZkAesCtr,
@@ -36,96 +35,26 @@ pub struct Setup {
}
/// State after the TLS connection has been closed.
pub struct Closed {
pub struct Committed {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) mt: Multithread,
pub(crate) delta: Delta,
pub(crate) ctx: Context,
pub(crate) keys: SessionKeys,
pub(crate) vm: Zk,
pub(crate) server_ephemeral_key: ServerEphemKey,
pub(crate) connection_info: ConnectionInfo,
pub(crate) transcript_refs: TranscriptRefs,
}
opaque_debug::implement!(Closed);
/// Notarizing state.
pub struct Notarize {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) _mt: Multithread,
pub(crate) delta: Delta,
pub(crate) ctx: Context,
pub(crate) _keys: SessionKeys,
pub(crate) vm: Zk,
pub(crate) server_ephemeral_key: ServerEphemKey,
pub(crate) connection_info: ConnectionInfo,
pub(crate) transcript_refs: TranscriptRefs,
}
opaque_debug::implement!(Notarize);
impl From<Closed> for Notarize {
fn from(value: Closed) -> Self {
Self {
mux_ctrl: value.mux_ctrl,
mux_fut: value.mux_fut,
_mt: value.mt,
delta: value.delta,
ctx: value.ctx,
_keys: value.keys,
vm: value.vm,
server_ephemeral_key: value.server_ephemeral_key,
connection_info: value.connection_info,
transcript_refs: value.transcript_refs,
}
}
}
/// Verifying state.
pub struct Verify {
pub(crate) mux_ctrl: MuxControl,
pub(crate) mux_fut: MuxFuture,
pub(crate) _mt: Multithread,
pub(crate) ctx: Context,
pub(crate) _keys: SessionKeys,
pub(crate) vm: Zk,
pub(crate) server_ephemeral_key: ServerEphemKey,
pub(crate) connection_info: ConnectionInfo,
pub(crate) transcript_refs: TranscriptRefs,
}
opaque_debug::implement!(Verify);
impl From<Closed> for Verify {
fn from(value: Closed) -> Self {
Self {
mux_ctrl: value.mux_ctrl,
mux_fut: value.mux_fut,
_mt: value.mt,
ctx: value.ctx,
_keys: value.keys,
vm: value.vm,
server_ephemeral_key: value.server_ephemeral_key,
connection_info: value.connection_info,
transcript_refs: value.transcript_refs,
}
}
}
opaque_debug::implement!(Committed);
impl VerifierState for Initialized {}
impl VerifierState for Setup {}
impl VerifierState for Closed {}
impl VerifierState for Notarize {}
impl VerifierState for Verify {}
impl VerifierState for Committed {}
mod sealed {
pub trait Sealed {}
impl Sealed for super::Initialized {}
impl Sealed for super::Setup {}
impl Sealed for super::Closed {}
impl Sealed for super::Notarize {}
impl Sealed for super::Verify {}
impl Sealed for super::Committed {}
}

View File

@@ -1,133 +0,0 @@
//! This module handles the verification phase of the verifier.
//!
//! The TLS verifier is an application-specific verifier.
use crate::SessionInfo;
use super::{state::Verify as VerifyState, Verifier, VerifierError};
use mpz_memory_core::MemoryExt;
use mpz_vm_core::Execute;
use serio::stream::IoStreamExt;
use tlsn_common::msg::ServerIdentityProof;
use tlsn_core::transcript::{Direction, PartialTranscript};
use tracing::{info, instrument};
impl Verifier<VerifyState> {
/// Receives the **purported** transcript from the Prover.
///
/// # Warning
///
/// The content of the received transcript can not be considered authentic
/// until after finalization.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn receive(&mut self) -> Result<PartialTranscript, VerifierError> {
self.state
.mux_fut
.poll_with(async {
// Receive partial transcript from the prover.
let partial_transcript: PartialTranscript =
self.state.ctx.io_mut().expect_next().await?;
info!("Received partial transcript from prover");
// Check ranges.
if partial_transcript.len_sent()
!= self.state.connection_info.transcript_length.sent as usize
|| partial_transcript.len_received()
!= self.state.connection_info.transcript_length.received as usize
{
return Err(VerifierError::verify(
"prover sent transcript with incorrect length",
));
}
// Now verify the transcript parts which the prover wants to reveal.
let sent_refs = self
.state
.transcript_refs
.get(Direction::Sent, partial_transcript.sent_authed())
.expect("index is in bounds");
let recv_refs = self
.state
.transcript_refs
.get(Direction::Received, partial_transcript.received_authed())
.expect("index is in bounds");
let plaintext_futs = sent_refs
.into_iter()
.chain(recv_refs)
.map(|slice| self.state.vm.decode(slice).map_err(VerifierError::zk))
.collect::<Result<Vec<_>, _>>()?;
self.state.vm.flush(&mut self.state.ctx).await.unwrap();
let mut authenticated_data = Vec::new();
for mut fut in plaintext_futs {
let plaintext = fut
.try_recv()
.map_err(VerifierError::zk)?
.expect("plaintext should be decoded");
authenticated_data.extend_from_slice(&plaintext);
}
// Check that the purported data in the partial transcript is
// correct.
if authenticated_data
.into_iter()
.zip(
partial_transcript
.iter(Direction::Sent)
.chain(partial_transcript.iter(Direction::Received)),
)
.any(|(a, b)| a != b)
{
return Err(VerifierError::verify("purported transcript is incorrect"));
}
info!("Successfully verified purported transcript");
Ok::<_, VerifierError>(partial_transcript)
})
.await
}
/// Verifies the TLS session.
#[instrument(parent = &self.span, level = "info", skip_all, err)]
pub async fn finalize(self) -> Result<SessionInfo, VerifierError> {
let VerifyState {
mux_ctrl,
mut mux_fut,
mut ctx,
server_ephemeral_key,
connection_info,
..
} = self.state;
let ServerIdentityProof {
name: server_name,
data,
} = mux_fut.poll_with(ctx.io_mut().expect_next()).await?;
// Verify the server identity data.
data.verify_with_provider(
self.config.crypto_provider(),
connection_info.time,
&server_ephemeral_key,
&server_name,
)
.map_err(VerifierError::verify)?;
info!("Successfully verified session");
if !mux_fut.is_complete() {
mux_ctrl.close();
mux_fut.await?;
}
Ok(SessionInfo {
server_name,
connection_info,
})
}
}

View File

@@ -5,7 +5,8 @@ use futures::{AsyncReadExt, AsyncWriteExt, Future};
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
use tlsn_core::{
attestation::AttestationConfig, signing::SignatureAlgId, transcript::Idx, CryptoProvider,
attestation::AttestationConfig, signing::SignatureAlgId, CryptoProvider, ProveConfig,
VerifyConfig,
};
use tlsn_prover::{Prover, ProverConfig};
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
@@ -90,7 +91,9 @@ async fn handle_verifier(io: TcpStream) -> Result<()> {
let verifier = Verifier::new(config);
verifier.verify(io.compat()).await?;
verifier
.verify(io.compat(), &VerifyConfig::default())
.await?;
Ok(())
}
@@ -120,6 +123,7 @@ async fn handle_notary(io: TcpStream) -> Result<()> {
let attestation_config = builder.build().unwrap();
#[allow(deprecated)]
verifier.notarize(io.compat(), &attestation_config).await?;
Ok(())
@@ -176,18 +180,20 @@ async fn handle_prover(io: TcpStream) -> Result<()> {
let mut response = vec![0u8; 1024];
tls_connection.read_to_end(&mut response).await.unwrap();
let mut prover = prover_task.await.unwrap().unwrap().start_prove();
let mut prover = prover_task.await.unwrap().unwrap();
let sent_transcript_len = prover.transcript().sent().len();
let recv_transcript_len = prover.transcript().received().len();
let sent_idx = Idx::new(0..sent_transcript_len - 1);
let recv_idx = Idx::new(2..recv_transcript_len);
let mut builder = ProveConfig::builder(prover.transcript());
// Reveal parts of the transcript
prover.prove_transcript(sent_idx, recv_idx).await.unwrap();
builder.reveal_sent(&(0..sent_transcript_len - 1)).unwrap();
builder.reveal_recv(&(2..recv_transcript_len)).unwrap();
prover.finalize().await.unwrap();
let config = builder.build().unwrap();
prover.prove(&config).await.unwrap();
prover.close().await.unwrap();
Ok(())
}

View File

@@ -7,11 +7,8 @@ use futures::TryFutureExt;
use http_body_util::{BodyExt, Full};
use hyper::body::Bytes;
use tls_client_async::TlsConnection;
use tlsn_core::{
request::RequestConfig,
transcript::{Idx, TranscriptCommitConfigBuilder},
};
use tlsn_prover::{state, Prover};
use tlsn_core::{request::RequestConfig, transcript::TranscriptCommitConfigBuilder};
use tlsn_prover::{state, ProveConfig, Prover};
use tracing::info;
use wasm_bindgen::{prelude::*, JsError};
use wasm_bindgen_futures::spawn_local;
@@ -31,7 +28,7 @@ pub struct JsProver {
enum State {
Initialized(Prover<state::Initialized>),
Setup(Prover<state::Setup>),
Closed(Prover<state::Closed>),
Committed(Prover<state::Committed>),
Complete,
Error,
}
@@ -96,21 +93,21 @@ impl JsProver {
info!("response received");
self.state = State::Closed(prover);
self.state = State::Committed(prover);
Ok(response)
}
/// Returns the transcript.
pub fn transcript(&self) -> Result<Transcript> {
let prover = self.state.try_as_closed()?;
let prover = self.state.try_as_committed()?;
Ok(Transcript::from(prover.transcript()))
}
/// Runs the notarization protocol.
pub async fn notarize(&mut self, commit: Commit) -> Result<NotarizationOutput> {
let mut prover = self.state.take().try_into_closed()?.start_notarize();
let mut prover = self.state.take().try_into_committed()?;
info!("starting notarization");
@@ -124,12 +121,17 @@ impl JsProver {
builder.commit_recv(&range)?;
}
let config = builder.build()?;
let transcript_commit = builder.build()?;
prover.transcript_commit(config);
let mut builder = RequestConfig::builder();
let request_config = RequestConfig::default();
let (attestation, secrets) = prover.finalize(&request_config).await?;
builder.transcript_commit(transcript_commit);
let request_config = builder.build()?;
#[allow(deprecated)]
let (attestation, secrets) = prover.notarize(&request_config).await?;
prover.close().await?;
info!("notarization complete");
@@ -143,15 +145,24 @@ impl JsProver {
/// Reveals data to the verifier and finalizes the protocol.
pub async fn reveal(&mut self, reveal: Reveal) -> Result<()> {
let mut prover = self.state.take().try_into_closed()?.start_prove();
let mut prover = self.state.take().try_into_committed()?;
info!("revealing data");
let sent = Idx::new(reveal.sent);
let recv = Idx::new(reveal.recv);
let mut builder = ProveConfig::builder(prover.transcript());
prover.prove_transcript(sent, recv).await?;
prover.finalize().await?;
for range in reveal.sent {
builder.reveal_sent(&range)?;
}
for range in reveal.recv {
builder.reveal_recv(&range)?;
}
let config = builder.build()?;
prover.prove(&config).await?;
prover.close().await?;
info!("Finalized");

View File

@@ -304,9 +304,9 @@ pub struct NotarizationOutput {
#[derive(Debug, Tsify, Serialize)]
#[tsify(into_wasm_abi)]
pub struct VerifierOutput {
pub server_name: String,
pub server_name: Option<String>,
pub connection_info: ConnectionInfo,
pub transcript: PartialTranscript,
pub transcript: Option<PartialTranscript>,
}
#[derive(Debug, Tsify, Serialize)]

View File

@@ -5,7 +5,7 @@ pub use config::VerifierConfig;
use enum_try_as_inner::EnumTryAsInner;
use tlsn_verifier::{
state::{self, Initialized},
Verifier,
Verifier, VerifyConfig,
};
use tracing::info;
use wasm_bindgen::prelude::*;
@@ -69,14 +69,19 @@ impl JsVerifier {
pub async fn verify(&mut self) -> Result<VerifierOutput> {
let (verifier, prover_conn) = self.state.take().try_into_connected()?;
let (transcript, info) = verifier.verify(prover_conn.into_io()).await?;
let mut verifier = verifier.setup(prover_conn.into_io()).await?.run().await?;
let connection_info = verifier.connection_info().clone();
let output = verifier.verify(&VerifyConfig::default()).await?;
verifier.close().await?;
self.state = State::Complete;
Ok(VerifierOutput {
server_name: info.server_name.as_str().to_string(),
connection_info: info.connection_info.into(),
transcript: transcript.into(),
server_name: output.server_name.map(|s| s.as_str().to_string()),
connection_info: connection_info.into(),
transcript: output.transcript.map(|t| t.into()),
})
}
}