diff --git a/crates/core/src/attestation/builder.rs b/crates/core/src/attestation/builder.rs index a2dc674ead..3ecf8b018f 100644 --- a/crates/core/src/attestation/builder.rs +++ b/crates/core/src/attestation/builder.rs @@ -245,64 +245,18 @@ mod test { use crate::{ connection::{HandshakeData, HandshakeDataV1_2}, - fixtures::{encoder_seed, encoding_provider, ConnectionFixture}, + fixtures::{ + encoder_seed, encoding_provider, request_fixture, ConnectionFixture, RequestFixture, + }, hash::Blake3, - request::RequestConfig, - transcript::{encoding::EncodingTree, Transcript, TranscriptCommitConfigBuilder}, + transcript::Transcript, }; use super::*; - fn request_and_connection() -> (Request, ConnectionFixture) { - let provider = CryptoProvider::default(); - - let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); - let (sent_len, recv_len) = transcript.len(); - // Plaintext encodings which the Prover obtained from GC evaluation - let encodings_provider = encoding_provider(GET_WITH_HEADER, OK_JSON); - - // At the end of the TLS connection the Prover holds the: - let ConnectionFixture { - server_name, - server_cert_data, - .. - } = ConnectionFixture::tlsnotary(transcript.length()); - - // Prover specifies the ranges it wants to commit to. - let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); - transcript_commitment_builder - .commit_sent(&(0..sent_len)) - .unwrap() - .commit_recv(&(0..recv_len)) - .unwrap(); - - let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); - - // Prover constructs encoding tree. - let encoding_tree = EncodingTree::new( - &Blake3::default(), - transcripts_commitment_config.iter_encoding(), - &encodings_provider, - &transcript.length(), - ) - .unwrap(); - - let request_config = RequestConfig::default(); - let mut request_builder = Request::builder(&request_config); - - request_builder - .server_name(server_name.clone()) - .server_cert_data(server_cert_data) - .transcript(transcript.clone()) - .encoding_tree(encoding_tree); - let (request, _) = request_builder.build(&provider).unwrap(); - - (request, ConnectionFixture::tlsnotary(transcript.length())) - } - #[fixture] #[once] - fn default_attestation_config() -> AttestationConfig { + fn attestation_config() -> AttestationConfig { AttestationConfig::builder() .supported_signature_algs([SignatureAlgId::SECP256K1]) .build() @@ -319,7 +273,16 @@ mod test { #[rstest] fn test_attestation_builder_accept_unsupported_signer() { - let (request, _) = request_and_connection(); + 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(), + ); + let attestation_config = AttestationConfig::builder() .supported_signature_algs([SignatureAlgId::SECP256R1]) .build() @@ -334,7 +297,15 @@ mod test { #[rstest] fn test_attestation_builder_accept_unsupported_hasher() { - let (request, _) = request_and_connection(); + 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(), + ); let attestation_config = AttestationConfig::builder() .supported_signature_algs([SignatureAlgId::SECP256K1]) @@ -351,7 +322,15 @@ mod test { #[rstest] fn test_attestation_builder_accept_unsupported_encoding_commitment() { - let (request, _) = request_and_connection(); + 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(), + ); let attestation_config = AttestationConfig::builder() .supported_signature_algs([SignatureAlgId::SECP256K1]) @@ -371,13 +350,19 @@ mod test { } #[rstest] - fn test_attestation_builder_sign_missing_signer( - default_attestation_config: &AttestationConfig, - ) { - let (request, _) = request_and_connection(); + fn test_attestation_builder_sign_missing_signer(attestation_config: &AttestationConfig) { + let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); + let connection = ConnectionFixture::tlsnotary(transcript.length()); - let attestation_builder = Attestation::builder(default_attestation_config) - .accept_request(request.clone()) + let RequestFixture { request, .. } = request_fixture( + transcript, + encoding_provider(GET_WITH_HEADER, OK_JSON), + connection, + Blake3::default(), + ); + + let attestation_builder = Attestation::builder(attestation_config) + .accept_request(request) .unwrap(); let mut provider = CryptoProvider::default(); @@ -389,13 +374,21 @@ mod test { #[rstest] fn test_attestation_builder_sign_missing_encoding_seed( - default_attestation_config: &AttestationConfig, + attestation_config: &AttestationConfig, crypto_provider: &CryptoProvider, ) { - let (request, connection) = request_and_connection(); + 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(), + ); - let mut attestation_builder = Attestation::builder(default_attestation_config) - .accept_request(request.clone()) + let mut attestation_builder = Attestation::builder(attestation_config) + .accept_request(request) .unwrap(); let ConnectionFixture { @@ -407,10 +400,10 @@ mod test { let HandshakeData::V1_2(HandshakeDataV1_2 { server_ephemeral_key, .. - }) = server_cert_data.handshake.clone(); + }) = server_cert_data.handshake; attestation_builder - .connection_info(connection_info.clone()) + .connection_info(connection_info) .server_ephemeral_key(server_ephemeral_key); let err = attestation_builder.build(crypto_provider).err().unwrap(); @@ -419,13 +412,21 @@ mod test { #[rstest] fn test_attestation_builder_sign_missing_server_ephemeral_key( - default_attestation_config: &AttestationConfig, + attestation_config: &AttestationConfig, crypto_provider: &CryptoProvider, ) { - let (request, connection) = request_and_connection(); + 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(), + ); - let mut attestation_builder = Attestation::builder(default_attestation_config) - .accept_request(request.clone()) + let mut attestation_builder = Attestation::builder(attestation_config) + .accept_request(request) .unwrap(); let ConnectionFixture { @@ -433,7 +434,7 @@ mod test { } = connection; attestation_builder - .connection_info(connection_info.clone()) + .connection_info(connection_info) .encoding_seed(encoder_seed().to_vec()); let err = attestation_builder.build(crypto_provider).err().unwrap(); @@ -442,13 +443,21 @@ mod test { #[rstest] fn test_attestation_builder_sign_missing_connection_info( - default_attestation_config: &AttestationConfig, + attestation_config: &AttestationConfig, crypto_provider: &CryptoProvider, ) { - let (request, connection) = request_and_connection(); + let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); + let connection = ConnectionFixture::tlsnotary(transcript.length()); - let mut attestation_builder = Attestation::builder(default_attestation_config) - .accept_request(request.clone()) + let RequestFixture { request, .. } = request_fixture( + transcript, + encoding_provider(GET_WITH_HEADER, OK_JSON), + connection.clone(), + Blake3::default(), + ); + + let mut attestation_builder = Attestation::builder(attestation_config) + .accept_request(request) .unwrap(); let ConnectionFixture { @@ -458,7 +467,7 @@ mod test { let HandshakeData::V1_2(HandshakeDataV1_2 { server_ephemeral_key, .. - }) = server_cert_data.handshake.clone(); + }) = server_cert_data.handshake; attestation_builder .server_ephemeral_key(server_ephemeral_key) diff --git a/crates/core/src/fixtures.rs b/crates/core/src/fixtures.rs index 44ab905fd6..baa558ca2d 100644 --- a/crates/core/src/fixtures.rs +++ b/crates/core/src/fixtures.rs @@ -8,14 +8,23 @@ use hex::FromHex; use p256::ecdsa::SigningKey; use crate::{ + attestation::{Attestation, AttestationConfig}, connection::{ Certificate, ConnectionInfo, HandshakeData, HandshakeDataV1_2, KeyType, ServerCertData, ServerEphemKey, ServerName, ServerSignature, SignatureScheme, TlsVersion, TranscriptLength, }, - transcript::{encoding::EncodingProvider, Transcript}, + hash::HashAlgorithm, + request::{Request, RequestConfig}, + signing::SignatureAlgId, + transcript::{ + encoding::{EncodingProvider, EncodingTree}, + Transcript, TranscriptCommitConfigBuilder, + }, + CryptoProvider, }; /// A fixture containing various TLS connection data. +#[derive(Clone)] #[allow(missing_docs)] pub struct ConnectionFixture { pub server_name: ServerName, @@ -134,3 +143,101 @@ pub fn encoder_seed() -> [u8; 32] { pub fn notary_signing_key() -> SigningKey { SigningKey::from_slice(&[1; 32]).unwrap() } + +/// A Request fixture used for testing. +#[allow(missing_docs)] +pub struct RequestFixture { + pub encoding_tree: EncodingTree, + pub request: Request, +} + +/// Returns a request fixture for testing. +pub fn request_fixture( + transcript: Transcript, + encodings_provider: impl EncodingProvider, + connection: ConnectionFixture, + encoding_hasher: impl HashAlgorithm, +) -> RequestFixture { + let provider = CryptoProvider::default(); + let (sent_len, recv_len) = transcript.len(); + + let ConnectionFixture { + server_name, + server_cert_data, + .. + } = connection; + + let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); + transcript_commitment_builder + .commit_sent(&(0..sent_len)) + .unwrap() + .commit_recv(&(0..recv_len)) + .unwrap(); + let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); + + // Prover constructs encoding tree. + let encoding_tree = EncodingTree::new( + &encoding_hasher, + transcripts_commitment_config.iter_encoding(), + &encodings_provider, + &transcript.length(), + ) + .unwrap(); + + let request_config = RequestConfig::default(); + let mut request_builder = Request::builder(&request_config); + request_builder + .server_name(server_name) + .server_cert_data(server_cert_data) + .transcript(transcript) + .encoding_tree(encoding_tree.clone()); + + let (request, _) = request_builder.build(&provider).unwrap(); + + RequestFixture { + encoding_tree, + request, + } +} + +/// Returns an attestation fixture for testing. +pub fn attestation_fixture( + request: Request, + connection: ConnectionFixture, + signature_alg: SignatureAlgId, + encoding_seed: Vec, +) -> Attestation { + let ConnectionFixture { + connection_info, + server_cert_data, + .. + } = connection; + + let HandshakeData::V1_2(HandshakeDataV1_2 { + server_ephemeral_key, + .. + }) = server_cert_data.handshake; + + let mut provider = CryptoProvider::default(); + match signature_alg { + SignatureAlgId::SECP256K1 => provider.signer.set_secp256k1(&[42u8; 32]).unwrap(), + SignatureAlgId::SECP256R1 => provider.signer.set_secp256r1(&[42u8; 32]).unwrap(), + _ => unimplemented!(), + }; + + let attestation_config = AttestationConfig::builder() + .supported_signature_algs([signature_alg]) + .build() + .unwrap(); + + let mut attestation_builder = Attestation::builder(&attestation_config) + .accept_request(request) + .unwrap(); + + attestation_builder + .connection_info(connection_info) + .server_ephemeral_key(server_ephemeral_key) + .encoding_seed(encoding_seed); + + attestation_builder.build(&provider).unwrap() +} diff --git a/crates/core/src/request.rs b/crates/core/src/request.rs index ae1bc4ebf7..0774ab5de7 100644 --- a/crates/core/src/request.rs +++ b/crates/core/src/request.rs @@ -90,115 +90,62 @@ pub struct InconsistentAttestation(String); #[cfg(test)] mod test { + use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; + use super::*; use crate::{ - attestation::{Attestation, AttestationConfig}, - connection::{HandshakeData, HandshakeDataV1_2, ServerCertOpening, TranscriptLength}, - fixtures::{encoder_seed, encoding_provider, ConnectionFixture}, + connection::{ServerCertOpening, TranscriptLength}, + fixtures::{ + attestation_fixture, encoder_seed, encoding_provider, request_fixture, + ConnectionFixture, RequestFixture, + }, hash::{Blake3, Hash, HashAlgId}, signing::SignatureAlgId, - transcript::{encoding::EncodingTree, Transcript, TranscriptCommitConfigBuilder}, + transcript::Transcript, CryptoProvider, }; - use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; - - fn attestation(payload: (Request, ConnectionFixture)) -> Attestation { - let (request, connection) = payload; - - let ConnectionFixture { - connection_info, - server_cert_data, - .. - } = connection; - - let HandshakeData::V1_2(HandshakeDataV1_2 { - server_ephemeral_key, - .. - }) = server_cert_data.handshake.clone(); - - let mut provider = CryptoProvider::default(); - provider.signer.set_secp256k1(&[42u8; 32]).unwrap(); - - let attestation_config = AttestationConfig::builder() - .supported_signature_algs([SignatureAlgId::SECP256K1]) - .build() - .unwrap(); - - let mut attestation_builder = Attestation::builder(&attestation_config) - .accept_request(request.clone()) - .unwrap(); - - attestation_builder - .connection_info(connection_info.clone()) - .server_ephemeral_key(server_ephemeral_key) - .encoding_seed(encoder_seed().to_vec()); - - attestation_builder.build(&provider).unwrap() - } - - fn request_and_connection() -> (Request, ConnectionFixture) { - let provider = CryptoProvider::default(); - - let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); - let (sent_len, recv_len) = transcript.len(); - // Plaintext encodings which the Prover obtained from GC evaluation. - let encodings_provider = encoding_provider(GET_WITH_HEADER, OK_JSON); - - // At the end of the TLS connection the Prover holds the: - let ConnectionFixture { - server_name, - server_cert_data, - .. - } = ConnectionFixture::tlsnotary(transcript.length()); - - // Prover specifies the ranges it wants to commit to. - let mut transcript_commitment_builder = TranscriptCommitConfigBuilder::new(&transcript); - transcript_commitment_builder - .commit_sent(&(0..sent_len)) - .unwrap() - .commit_recv(&(0..recv_len)) - .unwrap(); - - let transcripts_commitment_config = transcript_commitment_builder.build().unwrap(); - - // Prover constructs encoding tree. - let encoding_tree = EncodingTree::new( - &Blake3::default(), - transcripts_commitment_config.iter_encoding(), - &encodings_provider, - &transcript.length(), - ) - .unwrap(); - - let request_config = RequestConfig::default(); - let mut request_builder = Request::builder(&request_config); - - request_builder - .server_name(server_name.clone()) - .server_cert_data(server_cert_data) - .transcript(transcript.clone()) - .encoding_tree(encoding_tree); - let (request, _) = request_builder.build(&provider).unwrap(); - - (request, ConnectionFixture::tlsnotary(transcript.length())) - } - #[test] fn test_success() { - let (request, connection) = request_and_connection(); - - let attestation = attestation((request.clone(), connection)); + 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(), + ); + + let attestation = attestation_fixture( + request.clone(), + connection, + SignatureAlgId::SECP256K1, + encoder_seed().to_vec(), + ); assert!(request.validate(&attestation).is_ok()) } #[test] fn test_wrong_signature_alg() { - let (mut request, connection) = request_and_connection(); - - let attestation = attestation((request.clone(), connection)); + 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(), + ); + + let attestation = attestation_fixture( + request.clone(), + connection, + SignatureAlgId::SECP256K1, + encoder_seed().to_vec(), + ); request.signature_alg = SignatureAlgId::SECP256R1; @@ -208,9 +155,22 @@ mod test { #[test] fn test_wrong_hash_alg() { - let (mut request, connection) = request_and_connection(); - - let attestation = attestation((request.clone(), connection)); + 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(), + ); + + let attestation = attestation_fixture( + request.clone(), + connection, + SignatureAlgId::SECP256K1, + encoder_seed().to_vec(), + ); request.hash_alg = HashAlgId::SHA256; @@ -220,9 +180,22 @@ mod test { #[test] fn test_wrong_server_commitment() { - let (mut request, connection) = request_and_connection(); - - let attestation = attestation((request.clone(), connection)); + 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(), + ); + + let attestation = attestation_fixture( + request.clone(), + connection, + SignatureAlgId::SECP256K1, + encoder_seed().to_vec(), + ); let ConnectionFixture { server_cert_data, .. @@ -242,9 +215,22 @@ mod test { #[test] fn test_wrong_encoding_commitment_root() { - let (mut request, connection) = request_and_connection(); - - let attestation = attestation((request.clone(), connection)); + 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(), + ); + + let attestation = attestation_fixture( + request.clone(), + connection, + SignatureAlgId::SECP256K1, + encoder_seed().to_vec(), + ); request.encoding_commitment_root = Some(TypedHash { alg: HashAlgId::BLAKE3, diff --git a/crates/core/src/transcript.rs b/crates/core/src/transcript.rs index 6b14dc36a2..61e3758759 100644 --- a/crates/core/src/transcript.rs +++ b/crates/core/src/transcript.rs @@ -611,7 +611,7 @@ mod tests { } #[rstest] - fn test_get_subsequence(transcript: Transcript) { + fn test_transcript_get_subsequence(transcript: Transcript) { let subseq = transcript .get(Direction::Received, &Idx(RangeSet::from([0..4, 7..10]))) .unwrap(); @@ -631,4 +631,207 @@ mod tests { let subseq = transcript.get(Direction::Sent, &Idx(RangeSet::from([0..4, 7..10, 11..13]))); assert_eq!(subseq, None); } + + #[rstest] + fn test_transcript_to_partial_success(transcript: Transcript) { + let partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7)); + assert_eq!(partial.sent_unsafe(), [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + assert_eq!( + partial.received_unsafe(), + [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0] + ); + } + + #[rstest] + #[should_panic] + fn test_transcript_to_partial_failure(transcript: Transcript) { + let _ = transcript.to_partial(Idx::new(0..14), Idx::new(3..7)); + } + + #[rstest] + fn test_partial_transcript_contains(transcript: Transcript) { + let partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7)); + assert!(partial.contains(Direction::Sent, &Idx::new([0..5, 7..10]))); + assert!(!partial.contains(Direction::Received, &Idx::new([4..6, 7..13]))) + } + + #[rstest] + fn test_partial_transcript_unauthed(transcript: Transcript) { + let partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7)); + assert_eq!(partial.sent_unauthed(), Idx::new(2..12)); + assert_eq!(partial.received_unauthed(), Idx::new([0..3, 7..12])); + } + + #[rstest] + fn test_partial_transcript_union_success(transcript: Transcript) { + // Non overlapping ranges. + let mut simple_partial = transcript.to_partial(Idx::new(0..2), Idx::new(3..7)); + + let other_simple_partial = transcript.to_partial(Idx::new(3..5), Idx::new(1..2)); + + simple_partial.union_transcript(&other_simple_partial); + + assert_eq!( + simple_partial.sent_unsafe(), + [0, 1, 0, 3, 4, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + simple_partial.received_unsafe(), + [0, 1, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0] + ); + assert_eq!(simple_partial.sent_authed(), &Idx::new([0..2, 3..5])); + assert_eq!(simple_partial.received_authed(), &Idx::new([1..2, 3..7])); + + // Overwrite with another partial transcript. + + let another_simple_partial = transcript.to_partial(Idx::new(1..4), Idx::new(6..9)); + + simple_partial.union_transcript(&another_simple_partial); + + assert_eq!( + simple_partial.sent_unsafe(), + [0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + simple_partial.received_unsafe(), + [0, 1, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0] + ); + assert_eq!(simple_partial.sent_authed(), &Idx::new(0..5)); + assert_eq!(simple_partial.received_authed(), &Idx::new([1..2, 3..9])); + + // Overlapping ranges. + let mut overlap_partial = transcript.to_partial(Idx::new(4..6), Idx::new(3..7)); + + let other_overlap_partial = transcript.to_partial(Idx::new(3..5), Idx::new(5..9)); + + overlap_partial.union_transcript(&other_overlap_partial); + + assert_eq!( + overlap_partial.sent_unsafe(), + [0, 0, 0, 3, 4, 5, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + overlap_partial.received_unsafe(), + [0, 0, 0, 3, 4, 5, 6, 7, 8, 0, 0, 0] + ); + assert_eq!(overlap_partial.sent_authed(), &Idx::new([3..5, 4..6])); + assert_eq!(overlap_partial.received_authed(), &Idx::new([3..7, 5..9])); + + // Equal ranges. + let mut equal_partial = transcript.to_partial(Idx::new(4..6), Idx::new(3..7)); + + let other_equal_partial = transcript.to_partial(Idx::new(4..6), Idx::new(3..7)); + + equal_partial.union_transcript(&other_equal_partial); + + assert_eq!( + equal_partial.sent_unsafe(), + [0, 0, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0] + ); + assert_eq!( + equal_partial.received_unsafe(), + [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0] + ); + assert_eq!(equal_partial.sent_authed(), &Idx::new(4..6)); + assert_eq!(equal_partial.received_authed(), &Idx::new(3..7)); + + // Subset ranges. + let mut subset_partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11)); + + let other_subset_partial = transcript.to_partial(Idx::new(6..9), Idx::new(5..6)); + + subset_partial.union_transcript(&other_subset_partial); + + assert_eq!( + subset_partial.sent_unsafe(), + [0, 0, 0, 0, 4, 5, 6, 7, 8, 9, 0, 0] + ); + assert_eq!( + subset_partial.received_unsafe(), + [0, 0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 0] + ); + assert_eq!(subset_partial.sent_authed(), &Idx::new(4..10)); + assert_eq!(subset_partial.received_authed(), &Idx::new(3..11)); + } + + #[rstest] + #[should_panic] + fn test_partial_transcript_union_failure(transcript: Transcript) { + let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11)); + + let other_transcript = Transcript::new( + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], + ); + + let other_partial = other_transcript.to_partial(Idx::new(6..9), Idx::new(5..6)); + + partial.union_transcript(&other_partial); + } + + #[rstest] + fn test_partial_transcript_union_subseq_success(transcript: Transcript) { + let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11)); + let sent_seq = Subsequence::new(Idx::new([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap(); + let recv_seq = Subsequence::new(Idx::new([0..4, 5..7]), [0, 1, 2, 3, 5, 6].into()).unwrap(); + + partial.union_subsequence(Direction::Sent, &sent_seq); + partial.union_subsequence(Direction::Received, &recv_seq); + + assert_eq!(partial.sent_unsafe(), [0, 1, 2, 0, 4, 5, 6, 7, 8, 9, 0, 0]); + assert_eq!( + partial.received_unsafe(), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0] + ); + assert_eq!(partial.sent_authed(), &Idx::new([0..3, 4..10])); + assert_eq!(partial.received_authed(), &Idx::new(0..11)); + + // Overwrite with another subseq. + let other_sent_seq = Subsequence::new(Idx::new(0..3), [3, 2, 1].into()).unwrap(); + + partial.union_subsequence(Direction::Sent, &other_sent_seq); + assert_eq!(partial.sent_unsafe(), [3, 2, 1, 0, 4, 5, 6, 7, 8, 9, 0, 0]); + assert_eq!(partial.sent_authed(), &Idx::new([0..3, 4..10])); + } + + #[rstest] + #[should_panic] + fn test_partial_transcript_union_subseq_failure(transcript: Transcript) { + let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..11)); + + let sent_seq = Subsequence::new(Idx::new([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap(); + + partial.union_subsequence(Direction::Sent, &sent_seq); + } + + #[rstest] + fn test_partial_transcript_set_unauthed_range(transcript: Transcript) { + let mut partial = transcript.to_partial(Idx::new(4..10), Idx::new(3..7)); + + partial.set_unauthed_range(7, Direction::Sent, 2..5); + partial.set_unauthed_range(5, Direction::Sent, 0..2); + partial.set_unauthed_range(3, Direction::Received, 4..6); + partial.set_unauthed_range(1, Direction::Received, 3..7); + + assert_eq!(partial.sent_unsafe(), [5, 5, 7, 7, 4, 5, 6, 7, 8, 9, 0, 0]); + assert_eq!( + partial.received_unsafe(), + [0, 0, 0, 3, 4, 5, 6, 0, 0, 0, 0, 0] + ); + } + + #[rstest] + #[should_panic] + fn test_subsequence_new_invalid_len() { + let _ = Subsequence::new(Idx::new([0..3, 5..8]), [0, 1, 2, 5, 6].into()).unwrap(); + } + + #[rstest] + #[should_panic] + fn test_subsequence_copy_to_invalid_len() { + let seq = Subsequence::new(Idx::new([0..3, 5..7]), [0, 1, 2, 5, 6].into()).unwrap(); + + let mut data: [u8; 3] = [0, 1, 2]; + seq.copy_to(&mut data); + } } diff --git a/crates/core/src/transcript/encoding/proof.rs b/crates/core/src/transcript/encoding/proof.rs index 231340c4b2..d87b63d3bb 100644 --- a/crates/core/src/transcript/encoding/proof.rs +++ b/crates/core/src/transcript/encoding/proof.rs @@ -182,3 +182,144 @@ impl From for EncodingProofError { Self::new(ErrorKind::Proof, error) } } + +#[cfg(test)] +mod test { + use tlsn_data_fixtures::http::{request::POST_JSON, response::OK_JSON}; + + use crate::{ + fixtures::{encoder_seed, encoding_provider}, + hash::Blake3, + transcript::{encoding::EncodingTree, Idx, Transcript}, + }; + + use super::*; + + struct EncodingFixture { + transcript: Transcript, + proof: EncodingProof, + commitment: EncodingCommitment, + } + + fn new_encoding_fixture(seed: Vec) -> EncodingFixture { + let transcript = Transcript::new(POST_JSON, OK_JSON); + + let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len())); + 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 proof = tree + .proof(&transcript, [&idx_0, &idx_1].into_iter()) + .unwrap(); + + let commitment = EncodingCommitment { + root: tree.root(), + seed, + }; + + EncodingFixture { + transcript, + proof, + commitment, + } + } + + #[test] + fn test_verify_encoding_proof_invalid_seed() { + let EncodingFixture { + transcript, + proof, + commitment, + } = new_encoding_fixture(encoder_seed().to_vec().split_off(1)); + + let err = proof + .verify_with_provider( + &CryptoProvider::default(), + &transcript.length(), + &commitment, + ) + .unwrap_err(); + + assert!(matches!(err.kind, ErrorKind::Commitment)); + } + + #[test] + fn test_verify_encoding_proof_out_of_range() { + let EncodingFixture { + transcript, + proof, + commitment, + } = new_encoding_fixture(encoder_seed().to_vec()); + + let err = proof + .verify_with_provider( + &CryptoProvider::default(), + &TranscriptLength { + sent: (transcript.len_of_direction(Direction::Sent) - 1) as u32, + received: (transcript.len_of_direction(Direction::Received) - 2) as u32, + }, + &commitment, + ) + .unwrap_err(); + + assert!(matches!(err.kind, ErrorKind::Proof)); + } + + #[test] + fn test_verify_encoding_proof_tampered_encoding_seq() { + let EncodingFixture { + transcript, + mut proof, + commitment, + } = new_encoding_fixture(encoder_seed().to_vec()); + + let Opening { seq, .. } = proof.openings.values_mut().next().unwrap(); + + *seq = Subsequence::new(Idx::new([0..3, 13..15]), [0, 1, 2, 5, 6].into()).unwrap(); + + let err = proof + .verify_with_provider( + &CryptoProvider::default(), + &transcript.length(), + &commitment, + ) + .unwrap_err(); + + assert!(matches!(err.kind, ErrorKind::Proof)); + } + + #[test] + fn test_verify_encoding_proof_tampered_encoding_blinder() { + let EncodingFixture { + transcript, + mut proof, + commitment, + } = new_encoding_fixture(encoder_seed().to_vec()); + + let Opening { blinder, .. } = proof.openings.values_mut().next().unwrap(); + + *blinder = rand::random(); + + let err = proof + .verify_with_provider( + &CryptoProvider::default(), + &transcript.length(), + &commitment, + ) + .unwrap_err(); + + assert!(matches!(err.kind, ErrorKind::Proof)); + } +} diff --git a/crates/core/src/transcript/encoding/tree.rs b/crates/core/src/transcript/encoding/tree.rs index 35ecbf2c4e..3bcec07067 100644 --- a/crates/core/src/transcript/encoding/tree.rs +++ b/crates/core/src/transcript/encoding/tree.rs @@ -287,6 +287,22 @@ mod tests { assert_eq!(partial_transcript.received_unsafe(), transcript.received()); } + #[test] + fn test_encoding_tree_proof_missing_leaf() { + let transcript = Transcript::new(POST_JSON, OK_JSON); + + let idx_0 = (Direction::Sent, Idx::new(0..POST_JSON.len())); + let idx_1 = (Direction::Received, Idx::new(0..4)); + let idx_2 = (Direction::Received, Idx::new(4..OK_JSON.len())); + + let tree = new_tree(&transcript, [&idx_0, &idx_1].into_iter()).unwrap(); + + let result = tree + .proof(&transcript, [&idx_0, &idx_1, &idx_2].into_iter()) + .unwrap_err(); + assert!(matches!(result, EncodingTreeError::MissingLeaf { .. })); + } + #[test] fn test_encoding_tree_out_of_bounds() { let transcript = Transcript::new(POST_JSON, OK_JSON); @@ -317,14 +333,5 @@ mod tests { ) .unwrap_err(); assert!(matches!(result, EncodingTreeError::MissingEncoding { .. })); - - let result = EncodingTree::new( - &Blake3::default(), - [(Direction::Sent, Idx::new(0..8))].iter(), - &provider, - &transcript_length, - ) - .unwrap_err(); - assert!(matches!(result, EncodingTreeError::MissingEncoding { .. })); } } diff --git a/crates/core/src/transcript/proof.rs b/crates/core/src/transcript/proof.rs index 980987dd68..ae9a68dc1e 100644 --- a/crates/core/src/transcript/proof.rs +++ b/crates/core/src/transcript/proof.rs @@ -357,10 +357,21 @@ impl fmt::Display for TranscriptProofBuilderError { #[cfg(test)] mod tests { + use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON}; + + use crate::{ + fixtures::{ + attestation_fixture, encoder_seed, encoding_provider, request_fixture, + ConnectionFixture, RequestFixture, + }, + hash::Blake3, + signing::SignatureAlgId, + }; + use super::*; #[test] - fn test_range_out_of_bounds() { + fn test_reveal_range_out_of_bounds() { let transcript = Transcript::new( [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], @@ -368,7 +379,83 @@ mod tests { let index = Index::default(); let mut builder = TranscriptProofBuilder::new(&transcript, None, &index); - assert!(builder.reveal(&(10..15), Direction::Sent).is_err()); - assert!(builder.reveal(&(10..15), Direction::Received).is_err()); + let err = builder.reveal(&(10..15), Direction::Sent).err().unwrap(); + assert!(matches!(err.kind, BuilderErrorKind::Index)); + + let err = builder + .reveal(&(10..15), Direction::Received) + .err() + .unwrap(); + assert!(matches!(err.kind, BuilderErrorKind::Index)); + } + + #[test] + fn test_reveal_missing_encoding_tree() { + let transcript = Transcript::new( + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + ); + let index = Index::default(); + let mut builder = TranscriptProofBuilder::new(&transcript, None, &index); + + let err = builder.reveal_recv(&(9..11)).err().unwrap(); + assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment)); + } + + #[test] + fn test_reveal_missing_encoding_commitment_range() { + let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); + let connection = ConnectionFixture::tlsnotary(transcript.length()); + + let RequestFixture { encoding_tree, .. } = request_fixture( + transcript.clone(), + encoding_provider(GET_WITH_HEADER, OK_JSON), + connection, + Blake3::default(), + ); + + let index = Index::default(); + let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree), &index); + + let err = builder.reveal_recv(&(0..11)).err().unwrap(); + assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment)); + } + + #[test] + fn test_verify_missing_encoding_commitment() { + let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON); + let connection = ConnectionFixture::tlsnotary(transcript.length()); + + let RequestFixture { + mut request, + encoding_tree, + } = request_fixture( + transcript.clone(), + encoding_provider(GET_WITH_HEADER, OK_JSON), + connection.clone(), + Blake3::default(), + ); + + let index = Index::default(); + let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree), &index); + + builder.reveal_recv(&(0..transcript.len().1)).unwrap(); + + let transcript_proof = builder.build().unwrap(); + + request.encoding_commitment_root = None; + let attestation = attestation_fixture( + request, + connection, + SignatureAlgId::SECP256K1, + encoder_seed().to_vec(), + ); + + let provider = CryptoProvider::default(); + let err = transcript_proof + .verify_with_provider(&provider, &attestation.body) + .err() + .unwrap(); + assert!(matches!(err.kind, ErrorKind::Encoding)); } }