mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-08 22:28:15 -05:00
feat(tlsn): disclose encryption key (#1010)
Co-authored-by: th4s <th4s@metavoid.xyz>
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -7150,6 +7150,8 @@ dependencies = [
|
|||||||
name = "tlsn"
|
name = "tlsn"
|
||||||
version = "0.1.0-alpha.13-pre"
|
version = "0.1.0-alpha.13-pre"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes 0.8.4",
|
||||||
|
"ctr 0.9.2",
|
||||||
"derive_builder 0.12.0",
|
"derive_builder 0.12.0",
|
||||||
"futures",
|
"futures",
|
||||||
"ghash 0.5.1",
|
"ghash 0.5.1",
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ mpz-ot = { workspace = true }
|
|||||||
mpz-vm-core = { workspace = true }
|
mpz-vm-core = { workspace = true }
|
||||||
mpz-zk = { workspace = true }
|
mpz-zk = { workspace = true }
|
||||||
|
|
||||||
|
aes = { workspace = true }
|
||||||
|
ctr = { workspace = true }
|
||||||
derive_builder = { workspace = true }
|
derive_builder = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
opaque-debug = { workspace = true }
|
opaque-debug = { workspace = true }
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
use mpz_core::bitvec::BitVec;
|
|
||||||
use mpz_memory_core::{DecodeFutureTyped, MemoryExt, ViewExt, binary::Binary};
|
|
||||||
use mpz_vm_core::Vm;
|
|
||||||
use rangeset::{Difference, RangeSet, Subset};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
commit::transcript::ReferenceMap,
|
|
||||||
zk_aes_ctr::{ZkAesCtr, ZkAesCtrError},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) fn prove_plaintext(
|
|
||||||
vm: &mut dyn Vm<Binary>,
|
|
||||||
zk_aes: &mut ZkAesCtr,
|
|
||||||
plaintext: &[u8],
|
|
||||||
ranges: &RangeSet<usize>,
|
|
||||||
public: &RangeSet<usize>,
|
|
||||||
) -> Result<ReferenceMap, PlaintextAuthError> {
|
|
||||||
assert!(public.is_subset(ranges), "public is not a subset of ranges");
|
|
||||||
|
|
||||||
if ranges.is_empty() {
|
|
||||||
return Ok(ReferenceMap::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
let (plaintext_map, ciphertext_map) = zk_aes
|
|
||||||
.alloc_plaintext(vm, ranges)
|
|
||||||
.map_err(ErrorRepr::ZkAesCtr)?;
|
|
||||||
|
|
||||||
for (range, chunk) in plaintext_map
|
|
||||||
.index(&ranges.difference(public))
|
|
||||||
.expect("map contains all ranges")
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
vm.mark_private(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.assign(*chunk, plaintext[range].to_vec())
|
|
||||||
.map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.commit(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (range, chunk) in plaintext_map
|
|
||||||
.index(public)
|
|
||||||
.expect("map contains all ranges")
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
vm.mark_public(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.assign(*chunk, plaintext[range].to_vec())
|
|
||||||
.map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.commit(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (_, chunk) in ciphertext_map.iter() {
|
|
||||||
drop(vm.decode(*chunk).map_err(PlaintextAuthError::vm)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(plaintext_map)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn verify_plaintext(
|
|
||||||
vm: &mut dyn Vm<Binary>,
|
|
||||||
zk_aes: &mut ZkAesCtr,
|
|
||||||
plaintext: &[u8],
|
|
||||||
ciphertext: &[u8],
|
|
||||||
ranges: &RangeSet<usize>,
|
|
||||||
public: &RangeSet<usize>,
|
|
||||||
) -> Result<(ReferenceMap, PlaintextProof), PlaintextAuthError> {
|
|
||||||
assert!(public.is_subset(ranges), "public is not a subset of ranges");
|
|
||||||
|
|
||||||
if ranges.is_empty() {
|
|
||||||
return Ok((
|
|
||||||
ReferenceMap::default(),
|
|
||||||
PlaintextProof {
|
|
||||||
ciphertexts: vec![],
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (plaintext_map, ciphertext_map) = zk_aes
|
|
||||||
.alloc_plaintext(vm, ranges)
|
|
||||||
.map_err(ErrorRepr::ZkAesCtr)?;
|
|
||||||
|
|
||||||
for (_, chunk) in plaintext_map
|
|
||||||
.index(&ranges.difference(public))
|
|
||||||
.expect("map contains all ranges")
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
vm.mark_blind(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.commit(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (range, chunk) in plaintext_map
|
|
||||||
.index(public)
|
|
||||||
.expect("map contains all ranges")
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
vm.mark_public(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.assign(*chunk, plaintext[range].to_vec())
|
|
||||||
.map_err(PlaintextAuthError::vm)?;
|
|
||||||
vm.commit(*chunk).map_err(PlaintextAuthError::vm)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ciphertexts = Vec::new();
|
|
||||||
for (range, chunk) in ciphertext_map
|
|
||||||
.index(ranges)
|
|
||||||
.expect("map contains all ranges")
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
ciphertexts.push((
|
|
||||||
ciphertext[range].to_vec(),
|
|
||||||
vm.decode(*chunk).map_err(PlaintextAuthError::vm)?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((plaintext_map, PlaintextProof { ciphertexts }))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("plaintext authentication error: {0}")]
|
|
||||||
pub(crate) struct PlaintextAuthError(#[from] ErrorRepr);
|
|
||||||
|
|
||||||
impl PlaintextAuthError {
|
|
||||||
fn vm<E>(err: E) -> Self
|
|
||||||
where
|
|
||||||
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
|
||||||
{
|
|
||||||
Self(ErrorRepr::Vm(err.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
enum ErrorRepr {
|
|
||||||
#[error("vm error: {0}")]
|
|
||||||
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
|
|
||||||
#[error("zk aes ctr error: {0}")]
|
|
||||||
ZkAesCtr(ZkAesCtrError),
|
|
||||||
#[error("missing decoding")]
|
|
||||||
MissingDecoding,
|
|
||||||
#[error("invalid ciphertext")]
|
|
||||||
InvalidCiphertext,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub(crate) struct PlaintextProof {
|
|
||||||
// (expected, actual)
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
ciphertexts: Vec<(Vec<u8>, DecodeFutureTyped<BitVec, Vec<u8>>)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlaintextProof {
|
|
||||||
pub(crate) fn verify(self) -> Result<(), PlaintextAuthError> {
|
|
||||||
let Self {
|
|
||||||
ciphertexts: ciphertext,
|
|
||||||
} = self;
|
|
||||||
|
|
||||||
for (expected, mut actual) in ciphertext {
|
|
||||||
let actual = actual
|
|
||||||
.try_recv()
|
|
||||||
.map_err(PlaintextAuthError::vm)?
|
|
||||||
.ok_or(PlaintextAuthError(ErrorRepr::MissingDecoding))?;
|
|
||||||
|
|
||||||
if actual != expected {
|
|
||||||
return Err(PlaintextAuthError(ErrorRepr::InvalidCiphertext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,16 +4,15 @@
|
|||||||
#![deny(clippy::all)]
|
#![deny(clippy::all)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
pub(crate) mod commit;
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub(crate) mod context;
|
pub(crate) mod context;
|
||||||
pub(crate) mod encoding;
|
|
||||||
pub(crate) mod ghash;
|
pub(crate) mod ghash;
|
||||||
|
pub(crate) mod map;
|
||||||
pub(crate) mod mux;
|
pub(crate) mod mux;
|
||||||
pub mod prover;
|
pub mod prover;
|
||||||
pub(crate) mod tag;
|
pub(crate) mod tag;
|
||||||
|
pub(crate) mod transcript_internal;
|
||||||
pub mod verifier;
|
pub mod verifier;
|
||||||
pub(crate) mod zk_aes_ctr;
|
|
||||||
|
|
||||||
pub use tlsn_attestation as attestation;
|
pub use tlsn_attestation as attestation;
|
||||||
pub use tlsn_core::{connection, hash, transcript};
|
pub use tlsn_core::{connection, hash, transcript};
|
||||||
|
|||||||
@@ -3,15 +3,6 @@ use std::ops::Range;
|
|||||||
use mpz_memory_core::{Vector, binary::U8};
|
use mpz_memory_core::{Vector, binary::U8};
|
||||||
use rangeset::RangeSet;
|
use rangeset::RangeSet;
|
||||||
|
|
||||||
pub(crate) type ReferenceMap = RangeMap<Vector<U8>>;
|
|
||||||
|
|
||||||
/// References to the application plaintext in the transcript.
|
|
||||||
#[derive(Debug, Default, Clone)]
|
|
||||||
pub(crate) struct TranscriptRefs {
|
|
||||||
pub(crate) sent: ReferenceMap,
|
|
||||||
pub(crate) recv: ReferenceMap,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub(crate) struct RangeMap<T> {
|
pub(crate) struct RangeMap<T> {
|
||||||
map: Vec<(usize, T)>,
|
map: Vec<(usize, T)>,
|
||||||
@@ -44,6 +35,13 @@ where
|
|||||||
Self { map }
|
Self { map }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the keys of the map.
|
||||||
|
pub(crate) fn keys(&self) -> impl Iterator<Item = Range<usize>> {
|
||||||
|
self.map
|
||||||
|
.iter()
|
||||||
|
.map(|(idx, item)| *idx..*idx + item.length())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the length of the map.
|
/// Returns the length of the map.
|
||||||
pub(crate) fn len(&self) -> usize {
|
pub(crate) fn len(&self) -> usize {
|
||||||
self.map.iter().map(|(_, item)| item.length()).sum()
|
self.map.iter().map(|(_, item)| item.length()).sum()
|
||||||
@@ -2,7 +2,7 @@ use std::{error::Error, fmt};
|
|||||||
|
|
||||||
use mpc_tls::MpcTlsError;
|
use mpc_tls::MpcTlsError;
|
||||||
|
|
||||||
use crate::encoding::EncodingError;
|
use crate::transcript_internal::commit::encoding::EncodingError;
|
||||||
|
|
||||||
/// Error for [`Prover`](crate::Prover).
|
/// Error for [`Prover`](crate::Prover).
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
|||||||
@@ -13,10 +13,15 @@ use tlsn_core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commit::{auth::prove_plaintext, hash::prove_hash, transcript::TranscriptRefs},
|
|
||||||
encoding::{self, MacStore},
|
|
||||||
prover::ProverError,
|
prover::ProverError,
|
||||||
zk_aes_ctr::ZkAesCtr,
|
transcript_internal::{
|
||||||
|
TranscriptRefs,
|
||||||
|
auth::prove_plaintext,
|
||||||
|
commit::{
|
||||||
|
encoding::{self, MacStore},
|
||||||
|
hash::prove_hash,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) async fn prove<T: Vm<Binary> + MacStore + Send + Sync>(
|
pub(crate) async fn prove<T: Vm<Binary> + MacStore + Send + Sync>(
|
||||||
@@ -61,67 +66,51 @@ pub(crate) async fn prove<T: Vm<Binary> + MacStore + Send + Sync>(
|
|||||||
.await
|
.await
|
||||||
.map_err(ProverError::from)?;
|
.map_err(ProverError::from)?;
|
||||||
|
|
||||||
let mut auth_sent_ranges = RangeSet::default();
|
|
||||||
let mut auth_recv_ranges = RangeSet::default();
|
|
||||||
|
|
||||||
let (reveal_sent, reveal_recv) = config.reveal().cloned().unwrap_or_default();
|
let (reveal_sent, reveal_recv) = config.reveal().cloned().unwrap_or_default();
|
||||||
|
let (mut commit_sent, mut commit_recv) = (RangeSet::default(), RangeSet::default());
|
||||||
auth_sent_ranges.union_mut(&reveal_sent);
|
|
||||||
auth_recv_ranges.union_mut(&reveal_recv);
|
|
||||||
|
|
||||||
if let Some(commit_config) = config.transcript_commit() {
|
if let Some(commit_config) = config.transcript_commit() {
|
||||||
commit_config
|
commit_config
|
||||||
.iter_hash()
|
.iter_hash()
|
||||||
.for_each(|((direction, idx), _)| match direction {
|
.for_each(|((direction, idx), _)| match direction {
|
||||||
Direction::Sent => auth_sent_ranges.union_mut(idx),
|
Direction::Sent => commit_sent.union_mut(idx),
|
||||||
Direction::Received => auth_recv_ranges.union_mut(idx),
|
Direction::Received => commit_recv.union_mut(idx),
|
||||||
});
|
});
|
||||||
|
|
||||||
commit_config
|
commit_config
|
||||||
.iter_encoding()
|
.iter_encoding()
|
||||||
.for_each(|(direction, idx)| match direction {
|
.for_each(|(direction, idx)| match direction {
|
||||||
Direction::Sent => auth_sent_ranges.union_mut(idx),
|
Direction::Sent => commit_sent.union_mut(idx),
|
||||||
Direction::Received => auth_recv_ranges.union_mut(idx),
|
Direction::Received => commit_recv.union_mut(idx),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut zk_aes_sent = ZkAesCtr::new(
|
|
||||||
keys.client_write_key,
|
|
||||||
keys.client_write_iv,
|
|
||||||
tls_transcript
|
|
||||||
.sent()
|
|
||||||
.iter()
|
|
||||||
.filter(|record| record.typ == ContentType::ApplicationData),
|
|
||||||
);
|
|
||||||
let mut zk_aes_recv = ZkAesCtr::new(
|
|
||||||
keys.server_write_key,
|
|
||||||
keys.server_write_iv,
|
|
||||||
tls_transcript
|
|
||||||
.recv()
|
|
||||||
.iter()
|
|
||||||
.filter(|record| record.typ == ContentType::ApplicationData),
|
|
||||||
);
|
|
||||||
|
|
||||||
let sent_refs = prove_plaintext(
|
|
||||||
vm,
|
|
||||||
&mut zk_aes_sent,
|
|
||||||
transcript.sent(),
|
|
||||||
&auth_sent_ranges,
|
|
||||||
&reveal_sent,
|
|
||||||
)
|
|
||||||
.map_err(ProverError::commit)?;
|
|
||||||
let recv_refs = prove_plaintext(
|
|
||||||
vm,
|
|
||||||
&mut zk_aes_recv,
|
|
||||||
transcript.received(),
|
|
||||||
&auth_recv_ranges,
|
|
||||||
&reveal_recv,
|
|
||||||
)
|
|
||||||
.map_err(ProverError::commit)?;
|
|
||||||
|
|
||||||
let transcript_refs = TranscriptRefs {
|
let transcript_refs = TranscriptRefs {
|
||||||
sent: sent_refs,
|
sent: prove_plaintext(
|
||||||
recv: recv_refs,
|
vm,
|
||||||
|
keys.client_write_key,
|
||||||
|
keys.client_write_iv,
|
||||||
|
transcript.sent(),
|
||||||
|
tls_transcript
|
||||||
|
.sent()
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.typ == ContentType::ApplicationData),
|
||||||
|
&reveal_sent,
|
||||||
|
&commit_sent,
|
||||||
|
)
|
||||||
|
.map_err(ProverError::commit)?,
|
||||||
|
recv: prove_plaintext(
|
||||||
|
vm,
|
||||||
|
keys.server_write_key,
|
||||||
|
keys.server_write_iv,
|
||||||
|
transcript.received(),
|
||||||
|
tls_transcript
|
||||||
|
.recv()
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.typ == ContentType::ApplicationData),
|
||||||
|
&reveal_recv,
|
||||||
|
&commit_recv,
|
||||||
|
)
|
||||||
|
.map_err(ProverError::commit)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let hash_commitments = if let Some(commit_config) = config.transcript_commit()
|
let hash_commitments = if let Some(commit_config) = config.transcript_commit()
|
||||||
|
|||||||
16
crates/tlsn/src/transcript_internal.rs
Normal file
16
crates/tlsn/src/transcript_internal.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
pub(crate) mod auth;
|
||||||
|
pub(crate) mod commit;
|
||||||
|
|
||||||
|
use mpz_memory_core::{Vector, binary::U8};
|
||||||
|
|
||||||
|
use crate::map::RangeMap;
|
||||||
|
|
||||||
|
/// Maps transcript ranges to VM references.
|
||||||
|
pub(crate) type ReferenceMap = RangeMap<Vector<U8>>;
|
||||||
|
|
||||||
|
/// References to the application plaintext in the transcript.
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
|
pub(crate) struct TranscriptRefs {
|
||||||
|
pub(crate) sent: ReferenceMap,
|
||||||
|
pub(crate) recv: ReferenceMap,
|
||||||
|
}
|
||||||
455
crates/tlsn/src/transcript_internal/auth.rs
Normal file
455
crates/tlsn/src/transcript_internal/auth.rs
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use aes::Aes128;
|
||||||
|
use ctr::{
|
||||||
|
Ctr32BE,
|
||||||
|
cipher::{KeyIvInit, StreamCipher, StreamCipherSeek},
|
||||||
|
};
|
||||||
|
use mpz_circuits::circuits::{AES128, xor};
|
||||||
|
use mpz_core::bitvec::BitVec;
|
||||||
|
use mpz_memory_core::{
|
||||||
|
Array, DecodeFutureTyped, MemoryExt, Vector, ViewExt,
|
||||||
|
binary::{Binary, U8},
|
||||||
|
};
|
||||||
|
use mpz_vm_core::{Call, CallableExt, Vm};
|
||||||
|
use rangeset::{Difference, RangeSet, Union};
|
||||||
|
use tlsn_core::transcript::Record;
|
||||||
|
|
||||||
|
use crate::transcript_internal::ReferenceMap;
|
||||||
|
|
||||||
|
pub(crate) fn prove_plaintext<'a>(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
key: Array<U8, 16>,
|
||||||
|
iv: Array<U8, 4>,
|
||||||
|
plaintext: &[u8],
|
||||||
|
records: impl IntoIterator<Item = &'a Record>,
|
||||||
|
reveal: &RangeSet<usize>,
|
||||||
|
commit: &RangeSet<usize>,
|
||||||
|
) -> Result<ReferenceMap, PlaintextAuthError> {
|
||||||
|
let is_reveal_all = reveal == (0..plaintext.len());
|
||||||
|
|
||||||
|
let alloc_ranges = if is_reveal_all {
|
||||||
|
commit.clone()
|
||||||
|
} else {
|
||||||
|
// The plaintext is only partially revealed, so we need to authenticate in ZK.
|
||||||
|
commit.union(reveal)
|
||||||
|
};
|
||||||
|
|
||||||
|
let plaintext_refs = alloc_plaintext(vm, &alloc_ranges)?;
|
||||||
|
let records = RecordParams::from_iter(records).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if is_reveal_all {
|
||||||
|
drop(vm.decode(key).map_err(PlaintextAuthError::vm)?);
|
||||||
|
drop(vm.decode(iv).map_err(PlaintextAuthError::vm)?);
|
||||||
|
|
||||||
|
for (range, slice) in plaintext_refs.iter() {
|
||||||
|
vm.mark_public(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.assign(*slice, plaintext[range].to_vec())
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.commit(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let private = commit.difference(reveal);
|
||||||
|
for (_, slice) in plaintext_refs
|
||||||
|
.index(&private)
|
||||||
|
.expect("all ranges are allocated")
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
vm.mark_private(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, slice) in plaintext_refs
|
||||||
|
.index(reveal)
|
||||||
|
.expect("all ranges are allocated")
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
vm.mark_public(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (range, slice) in plaintext_refs.iter() {
|
||||||
|
vm.assign(*slice, plaintext[range].to_vec())
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.commit(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ciphertext = alloc_ciphertext(vm, key, iv, plaintext_refs.clone(), &records)?;
|
||||||
|
for (_, slice) in ciphertext.iter() {
|
||||||
|
drop(vm.decode(*slice).map_err(PlaintextAuthError::vm)?);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(plaintext_refs)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub(crate) fn verify_plaintext<'a>(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
key: Array<U8, 16>,
|
||||||
|
iv: Array<U8, 4>,
|
||||||
|
plaintext: &'a [u8],
|
||||||
|
ciphertext: &'a [u8],
|
||||||
|
records: impl IntoIterator<Item = &'a Record>,
|
||||||
|
reveal: &RangeSet<usize>,
|
||||||
|
commit: &RangeSet<usize>,
|
||||||
|
) -> Result<(ReferenceMap, PlaintextProof<'a>), PlaintextAuthError> {
|
||||||
|
let is_reveal_all = reveal == (0..plaintext.len());
|
||||||
|
|
||||||
|
let alloc_ranges = if is_reveal_all {
|
||||||
|
commit.clone()
|
||||||
|
} else {
|
||||||
|
// The plaintext is only partially revealed, so we need to authenticate in ZK.
|
||||||
|
commit.union(reveal)
|
||||||
|
};
|
||||||
|
|
||||||
|
let plaintext_refs = alloc_plaintext(vm, &alloc_ranges)?;
|
||||||
|
let records = RecordParams::from_iter(records).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let plaintext_proof = if is_reveal_all {
|
||||||
|
let key = vm.decode(key).map_err(PlaintextAuthError::vm)?;
|
||||||
|
let iv = vm.decode(iv).map_err(PlaintextAuthError::vm)?;
|
||||||
|
|
||||||
|
for (range, slice) in plaintext_refs.iter() {
|
||||||
|
vm.mark_public(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.assign(*slice, plaintext[range].to_vec())
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.commit(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaintextProof(ProofInner::WithKey {
|
||||||
|
key,
|
||||||
|
iv,
|
||||||
|
records,
|
||||||
|
plaintext,
|
||||||
|
ciphertext,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let private = commit.difference(reveal);
|
||||||
|
for (_, slice) in plaintext_refs
|
||||||
|
.index(&private)
|
||||||
|
.expect("all ranges are allocated")
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
vm.mark_blind(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (range, slice) in plaintext_refs
|
||||||
|
.index(reveal)
|
||||||
|
.expect("all ranges are allocated")
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
vm.mark_public(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.assign(*slice, plaintext[range].to_vec())
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, slice) in plaintext_refs.iter() {
|
||||||
|
vm.commit(*slice).map_err(PlaintextAuthError::vm)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ciphertext_map = alloc_ciphertext(vm, key, iv, plaintext_refs.clone(), &records)?;
|
||||||
|
|
||||||
|
let mut ciphertexts = Vec::new();
|
||||||
|
for (range, chunk) in ciphertext_map.iter() {
|
||||||
|
ciphertexts.push((
|
||||||
|
&ciphertext[range],
|
||||||
|
vm.decode(*chunk).map_err(PlaintextAuthError::vm)?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
PlaintextProof(ProofInner::WithZk { ciphertexts })
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((plaintext_refs, plaintext_proof))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_plaintext(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
ranges: &RangeSet<usize>,
|
||||||
|
) -> Result<ReferenceMap, PlaintextAuthError> {
|
||||||
|
let len = ranges.len();
|
||||||
|
|
||||||
|
let plaintext = vm.alloc_vec::<U8>(len).map_err(PlaintextAuthError::vm)?;
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
Ok(ReferenceMap::from_iter(ranges.iter_ranges().map(
|
||||||
|
move |range| {
|
||||||
|
let chunk = plaintext
|
||||||
|
.get(pos..pos + range.len())
|
||||||
|
.expect("length was checked");
|
||||||
|
pos += range.len();
|
||||||
|
(range.start, chunk)
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_ciphertext<'a>(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
key: Array<U8, 16>,
|
||||||
|
iv: Array<U8, 4>,
|
||||||
|
plaintext: ReferenceMap,
|
||||||
|
records: impl IntoIterator<Item = &'a RecordParams>,
|
||||||
|
) -> Result<ReferenceMap, PlaintextAuthError> {
|
||||||
|
let ranges = RangeSet::from(plaintext.keys().collect::<Vec<_>>());
|
||||||
|
|
||||||
|
let keystream = alloc_keystream(vm, key, iv, &ranges, records)?;
|
||||||
|
let mut builder = Call::builder(Arc::new(xor(ranges.len() * 8)));
|
||||||
|
for (_, slice) in plaintext.iter() {
|
||||||
|
builder = builder.arg(*slice);
|
||||||
|
}
|
||||||
|
for slice in keystream {
|
||||||
|
builder = builder.arg(slice);
|
||||||
|
}
|
||||||
|
let call = builder.build().expect("call should be valid");
|
||||||
|
|
||||||
|
let ciphertext: Vector<U8> = vm.call(call).map_err(PlaintextAuthError::vm)?;
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
Ok(ReferenceMap::from_iter(ranges.iter_ranges().map(
|
||||||
|
move |range| {
|
||||||
|
let chunk = ciphertext
|
||||||
|
.get(pos..pos + range.len())
|
||||||
|
.expect("length was checked");
|
||||||
|
pos += range.len();
|
||||||
|
(range.start, chunk)
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_keystream<'a>(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
key: Array<U8, 16>,
|
||||||
|
iv: Array<U8, 4>,
|
||||||
|
ranges: &RangeSet<usize>,
|
||||||
|
records: impl IntoIterator<Item = &'a RecordParams>,
|
||||||
|
) -> Result<Vec<Vector<U8>>, PlaintextAuthError> {
|
||||||
|
let mut keystream = Vec::new();
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut range_iter = ranges.iter_ranges();
|
||||||
|
let mut current_range = range_iter.next();
|
||||||
|
for record in records {
|
||||||
|
let mut explicit_nonce = None;
|
||||||
|
let mut current_block = None;
|
||||||
|
loop {
|
||||||
|
let Some(range) = current_range.take().or_else(|| range_iter.next()) else {
|
||||||
|
return Ok(keystream);
|
||||||
|
};
|
||||||
|
|
||||||
|
if range.start >= pos + record.len {
|
||||||
|
current_range = Some(range);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let explicit_nonce = if let Some(explicit_nonce) = explicit_nonce {
|
||||||
|
explicit_nonce
|
||||||
|
} else {
|
||||||
|
let nonce = alloc_explicit_nonce(vm, record.explicit_nonce.clone())?;
|
||||||
|
explicit_nonce = Some(nonce);
|
||||||
|
nonce
|
||||||
|
};
|
||||||
|
|
||||||
|
const BLOCK_SIZE: usize = 16;
|
||||||
|
let block_num = (range.start - pos) / BLOCK_SIZE;
|
||||||
|
let block = if let Some((current_block_num, block)) = current_block.take()
|
||||||
|
&& current_block_num == block_num
|
||||||
|
{
|
||||||
|
block
|
||||||
|
} else {
|
||||||
|
let block = alloc_block(vm, key, iv, explicit_nonce, block_num)?;
|
||||||
|
current_block = Some((block_num, block));
|
||||||
|
block
|
||||||
|
};
|
||||||
|
|
||||||
|
let start = (range.start - pos) % BLOCK_SIZE;
|
||||||
|
let end = (start + range.len()).min(BLOCK_SIZE);
|
||||||
|
let len = end - start;
|
||||||
|
|
||||||
|
keystream.push(block.get(start..end).expect("range is checked"));
|
||||||
|
|
||||||
|
// If the range is larger than a block, process the tail.
|
||||||
|
if range.len() > BLOCK_SIZE {
|
||||||
|
current_range = Some(range.start + len..range.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += record.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ErrorRepr::OutOfBounds.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_explicit_nonce(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
explicit_nonce: Vec<u8>,
|
||||||
|
) -> Result<Vector<U8>, PlaintextAuthError> {
|
||||||
|
const EXPLICIT_NONCE_LEN: usize = 8;
|
||||||
|
let nonce = vm
|
||||||
|
.alloc_vec::<U8>(EXPLICIT_NONCE_LEN)
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.mark_public(nonce).map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.assign(nonce, explicit_nonce)
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.commit(nonce).map_err(PlaintextAuthError::vm)?;
|
||||||
|
|
||||||
|
Ok(nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc_block(
|
||||||
|
vm: &mut dyn Vm<Binary>,
|
||||||
|
key: Array<U8, 16>,
|
||||||
|
iv: Array<U8, 4>,
|
||||||
|
explicit_nonce: Vector<U8>,
|
||||||
|
block: usize,
|
||||||
|
) -> Result<Vector<U8>, PlaintextAuthError> {
|
||||||
|
let ctr: Array<U8, 4> = vm.alloc().map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.mark_public(ctr).map_err(PlaintextAuthError::vm)?;
|
||||||
|
const START_CTR: u32 = 2;
|
||||||
|
vm.assign(ctr, (START_CTR + block as u32).to_be_bytes())
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
vm.commit(ctr).map_err(PlaintextAuthError::vm)?;
|
||||||
|
|
||||||
|
let block: Array<U8, 16> = vm
|
||||||
|
.call(
|
||||||
|
Call::builder(AES128.clone())
|
||||||
|
.arg(key)
|
||||||
|
.arg(iv)
|
||||||
|
.arg(explicit_nonce)
|
||||||
|
.arg(ctr)
|
||||||
|
.build()
|
||||||
|
.expect("call should be valid"),
|
||||||
|
)
|
||||||
|
.map_err(PlaintextAuthError::vm)?;
|
||||||
|
|
||||||
|
Ok(Vector::from(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RecordParams {
|
||||||
|
explicit_nonce: Vec<u8>,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RecordParams {
|
||||||
|
fn from_iter<'a>(records: impl IntoIterator<Item = &'a Record>) -> impl Iterator<Item = Self> {
|
||||||
|
records.into_iter().map(|record| Self {
|
||||||
|
explicit_nonce: record.explicit_nonce.clone(),
|
||||||
|
len: record.ciphertext.len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) struct PlaintextProof<'a>(ProofInner<'a>);
|
||||||
|
|
||||||
|
impl<'a> PlaintextProof<'a> {
|
||||||
|
pub(crate) fn verify(self) -> Result<(), PlaintextAuthError> {
|
||||||
|
match self.0 {
|
||||||
|
ProofInner::WithKey {
|
||||||
|
mut key,
|
||||||
|
mut iv,
|
||||||
|
records,
|
||||||
|
plaintext,
|
||||||
|
ciphertext,
|
||||||
|
} => {
|
||||||
|
let key = key
|
||||||
|
.try_recv()
|
||||||
|
.map_err(PlaintextAuthError::vm)?
|
||||||
|
.ok_or(ErrorRepr::MissingDecoding)?;
|
||||||
|
let iv = iv
|
||||||
|
.try_recv()
|
||||||
|
.map_err(PlaintextAuthError::vm)?
|
||||||
|
.ok_or(ErrorRepr::MissingDecoding)?;
|
||||||
|
|
||||||
|
verify_plaintext_with_key(key, iv, &records, plaintext, ciphertext)?;
|
||||||
|
}
|
||||||
|
ProofInner::WithZk { ciphertexts } => {
|
||||||
|
for (expected, mut actual) in ciphertexts {
|
||||||
|
let actual = actual
|
||||||
|
.try_recv()
|
||||||
|
.map_err(PlaintextAuthError::vm)?
|
||||||
|
.ok_or(PlaintextAuthError(ErrorRepr::MissingDecoding))?;
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
return Err(PlaintextAuthError(ErrorRepr::InvalidPlaintext));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProofInner<'a> {
|
||||||
|
WithKey {
|
||||||
|
key: DecodeFutureTyped<BitVec, [u8; 16]>,
|
||||||
|
iv: DecodeFutureTyped<BitVec, [u8; 4]>,
|
||||||
|
records: Vec<RecordParams>,
|
||||||
|
plaintext: &'a [u8],
|
||||||
|
ciphertext: &'a [u8],
|
||||||
|
},
|
||||||
|
WithZk {
|
||||||
|
// (expected, actual)
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
ciphertexts: Vec<(&'a [u8], DecodeFutureTyped<BitVec, Vec<u8>>)>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_plaintext_with_key<'a>(
|
||||||
|
key: [u8; 16],
|
||||||
|
iv: [u8; 4],
|
||||||
|
records: impl IntoIterator<Item = &'a RecordParams>,
|
||||||
|
plaintext: &[u8],
|
||||||
|
ciphertext: &[u8],
|
||||||
|
) -> Result<(), PlaintextAuthError> {
|
||||||
|
let mut pos = 0;
|
||||||
|
let mut text = Vec::new();
|
||||||
|
for record in records {
|
||||||
|
let mut full_iv = [0u8; 16];
|
||||||
|
full_iv[0..4].copy_from_slice(&iv);
|
||||||
|
full_iv[4..12].copy_from_slice(&record.explicit_nonce[..8]);
|
||||||
|
|
||||||
|
const START_CTR: u32 = 2;
|
||||||
|
let mut cipher = Ctr32BE::<Aes128>::new(&key.into(), &full_iv.into());
|
||||||
|
cipher
|
||||||
|
.try_seek(START_CTR * 16)
|
||||||
|
.expect("start counter is less than keystream length");
|
||||||
|
|
||||||
|
text.clear();
|
||||||
|
text.extend_from_slice(&plaintext[pos..pos + record.len]);
|
||||||
|
|
||||||
|
cipher.apply_keystream(&mut text);
|
||||||
|
|
||||||
|
if text != ciphertext[pos..pos + record.len] {
|
||||||
|
return Err(PlaintextAuthError(ErrorRepr::InvalidPlaintext));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += record.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
#[error("plaintext authentication error: {0}")]
|
||||||
|
pub(crate) struct PlaintextAuthError(#[from] ErrorRepr);
|
||||||
|
|
||||||
|
impl PlaintextAuthError {
|
||||||
|
fn vm<E>(err: E) -> Self
|
||||||
|
where
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
||||||
|
{
|
||||||
|
Self(ErrorRepr::Vm(err.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
enum ErrorRepr {
|
||||||
|
#[error("vm error: {0}")]
|
||||||
|
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
#[error("plaintext out of bounds of records. This should never happen and is an internal bug.")]
|
||||||
|
OutOfBounds,
|
||||||
|
#[error("missing decoding")]
|
||||||
|
MissingDecoding,
|
||||||
|
#[error("plaintext does not match ciphertext")]
|
||||||
|
InvalidPlaintext,
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
//! Plaintext commitment and proof of encryption.
|
//! Plaintext commitment and proof of encryption.
|
||||||
|
|
||||||
pub(crate) mod auth;
|
pub(crate) mod encoding;
|
||||||
pub(crate) mod hash;
|
pub(crate) mod hash;
|
||||||
pub(crate) mod transcript;
|
|
||||||
@@ -23,7 +23,10 @@ use tlsn_core::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::commit::transcript::{Item, RangeMap, ReferenceMap};
|
use crate::{
|
||||||
|
map::{Item, RangeMap},
|
||||||
|
transcript_internal::ReferenceMap,
|
||||||
|
};
|
||||||
|
|
||||||
/// Bytes of encoding, per byte.
|
/// Bytes of encoding, per byte.
|
||||||
const ENCODING_SIZE: usize = 128;
|
const ENCODING_SIZE: usize = 128;
|
||||||
@@ -18,7 +18,7 @@ use tlsn_core::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{Role, commit::transcript::TranscriptRefs};
|
use crate::{Role, transcript_internal::TranscriptRefs};
|
||||||
|
|
||||||
/// Future which will resolve to the committed hash values.
|
/// Future which will resolve to the committed hash values.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::encoding::EncodingError;
|
|
||||||
use mpc_tls::MpcTlsError;
|
|
||||||
use std::{error::Error, fmt};
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
use mpc_tls::MpcTlsError;
|
||||||
|
|
||||||
|
use crate::transcript_internal::commit::encoding::EncodingError;
|
||||||
|
|
||||||
/// Error for [`Verifier`](crate::Verifier).
|
/// Error for [`Verifier`](crate::Verifier).
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub struct VerifierError {
|
pub struct VerifierError {
|
||||||
|
|||||||
@@ -12,10 +12,15 @@ use tlsn_core::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commit::{auth::verify_plaintext, hash::verify_hash, transcript::TranscriptRefs},
|
transcript_internal::{
|
||||||
encoding::{self, KeyStore},
|
TranscriptRefs,
|
||||||
|
auth::verify_plaintext,
|
||||||
|
commit::{
|
||||||
|
encoding::{self, KeyStore},
|
||||||
|
hash::verify_hash,
|
||||||
|
},
|
||||||
|
},
|
||||||
verifier::VerifierError,
|
verifier::VerifierError,
|
||||||
zk_aes_ctr::ZkAesCtr,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) async fn verify<T: Vm<Binary> + KeyStore + Send + Sync>(
|
pub(crate) async fn verify<T: Vm<Binary> + KeyStore + Send + Sync>(
|
||||||
@@ -65,59 +70,47 @@ pub(crate) async fn verify<T: Vm<Binary> + KeyStore + Send + Sync>(
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut auth_sent_ranges = RangeSet::default();
|
let (mut commit_sent, mut commit_recv) = (RangeSet::default(), RangeSet::default());
|
||||||
let mut auth_recv_ranges = RangeSet::default();
|
|
||||||
|
|
||||||
auth_sent_ranges.union_mut(transcript.sent_authed());
|
|
||||||
auth_recv_ranges.union_mut(transcript.received_authed());
|
|
||||||
|
|
||||||
if let Some(commit_config) = transcript_commit.as_ref() {
|
if let Some(commit_config) = transcript_commit.as_ref() {
|
||||||
commit_config
|
commit_config
|
||||||
.iter_hash()
|
.iter_hash()
|
||||||
.for_each(|(direction, idx, _)| match direction {
|
.for_each(|(direction, idx, _)| match direction {
|
||||||
Direction::Sent => auth_sent_ranges.union_mut(idx),
|
Direction::Sent => commit_sent.union_mut(idx),
|
||||||
Direction::Received => auth_recv_ranges.union_mut(idx),
|
Direction::Received => commit_recv.union_mut(idx),
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some((sent, recv)) = commit_config.encoding() {
|
if let Some((sent, recv)) = commit_config.encoding() {
|
||||||
auth_sent_ranges.union_mut(sent);
|
commit_sent.union_mut(sent);
|
||||||
auth_recv_ranges.union_mut(recv);
|
commit_recv.union_mut(recv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut zk_aes_sent = ZkAesCtr::new(
|
let (sent_refs, sent_proof) = verify_plaintext(
|
||||||
|
vm,
|
||||||
keys.client_write_key,
|
keys.client_write_key,
|
||||||
keys.client_write_iv,
|
keys.client_write_iv,
|
||||||
|
transcript.sent_unsafe(),
|
||||||
|
&ciphertext_sent,
|
||||||
tls_transcript
|
tls_transcript
|
||||||
.sent()
|
.sent()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|record| record.typ == ContentType::ApplicationData),
|
.filter(|record| record.typ == ContentType::ApplicationData),
|
||||||
);
|
|
||||||
let mut zk_aes_recv = ZkAesCtr::new(
|
|
||||||
keys.server_write_key,
|
|
||||||
keys.server_write_iv,
|
|
||||||
tls_transcript
|
|
||||||
.recv()
|
|
||||||
.iter()
|
|
||||||
.filter(|record| record.typ == ContentType::ApplicationData),
|
|
||||||
);
|
|
||||||
|
|
||||||
let (sent_refs, sent_proof) = verify_plaintext(
|
|
||||||
vm,
|
|
||||||
&mut zk_aes_sent,
|
|
||||||
transcript.sent_unsafe(),
|
|
||||||
&ciphertext_sent,
|
|
||||||
&auth_sent_ranges,
|
|
||||||
transcript.sent_authed(),
|
transcript.sent_authed(),
|
||||||
|
&commit_sent,
|
||||||
)
|
)
|
||||||
.map_err(VerifierError::zk)?;
|
.map_err(VerifierError::zk)?;
|
||||||
let (recv_refs, recv_proof) = verify_plaintext(
|
let (recv_refs, recv_proof) = verify_plaintext(
|
||||||
vm,
|
vm,
|
||||||
&mut zk_aes_recv,
|
keys.server_write_key,
|
||||||
|
keys.server_write_iv,
|
||||||
transcript.received_unsafe(),
|
transcript.received_unsafe(),
|
||||||
&ciphertext_recv,
|
&ciphertext_recv,
|
||||||
&auth_recv_ranges,
|
tls_transcript
|
||||||
|
.recv()
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.typ == ContentType::ApplicationData),
|
||||||
transcript.received_authed(),
|
transcript.received_authed(),
|
||||||
|
&commit_recv,
|
||||||
)
|
)
|
||||||
.map_err(VerifierError::zk)?;
|
.map_err(VerifierError::zk)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,241 +0,0 @@
|
|||||||
use std::{ops::Range, sync::Arc};
|
|
||||||
|
|
||||||
use mpz_circuits::circuits::{AES128, xor};
|
|
||||||
use mpz_memory_core::{
|
|
||||||
Array, MemoryExt, Vector, ViewExt,
|
|
||||||
binary::{Binary, U8},
|
|
||||||
};
|
|
||||||
use mpz_vm_core::{Call, CallableExt, Vm};
|
|
||||||
use rangeset::RangeSet;
|
|
||||||
use tlsn_core::transcript::Record;
|
|
||||||
|
|
||||||
use crate::commit::transcript::ReferenceMap;
|
|
||||||
|
|
||||||
/// ZK AES-CTR encryption.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct ZkAesCtr {
|
|
||||||
key: Array<U8, 16>,
|
|
||||||
iv: Array<U8, 4>,
|
|
||||||
records: Vec<(usize, RecordState)>,
|
|
||||||
total_len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ZkAesCtr {
|
|
||||||
/// Creates a new instance.
|
|
||||||
pub(crate) fn new<'record>(
|
|
||||||
key: Array<U8, 16>,
|
|
||||||
iv: Array<U8, 4>,
|
|
||||||
records: impl IntoIterator<Item = &'record Record>,
|
|
||||||
) -> Self {
|
|
||||||
let mut pos = 0;
|
|
||||||
let mut record_state = Vec::new();
|
|
||||||
for record in records {
|
|
||||||
record_state.push((
|
|
||||||
pos,
|
|
||||||
RecordState {
|
|
||||||
explicit_nonce: Some(record.explicit_nonce.clone()),
|
|
||||||
explicit_nonce_ref: None,
|
|
||||||
range: pos..pos + record.ciphertext.len(),
|
|
||||||
},
|
|
||||||
));
|
|
||||||
pos += record.ciphertext.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
|
||||||
key,
|
|
||||||
iv,
|
|
||||||
records: record_state,
|
|
||||||
total_len: pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Allocates the plaintext for the provided ranges.
|
|
||||||
///
|
|
||||||
/// Returns a reference to the plaintext and the ciphertext.
|
|
||||||
pub(crate) fn alloc_plaintext(
|
|
||||||
&mut self,
|
|
||||||
vm: &mut dyn Vm<Binary>,
|
|
||||||
ranges: &RangeSet<usize>,
|
|
||||||
) -> Result<(ReferenceMap, ReferenceMap), ZkAesCtrError> {
|
|
||||||
let len = ranges.len();
|
|
||||||
|
|
||||||
if len > self.total_len {
|
|
||||||
return Err(ZkAesCtrError(ErrorRepr::TranscriptBounds {
|
|
||||||
len,
|
|
||||||
max: self.total_len,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let plaintext = vm.alloc_vec::<U8>(len).map_err(ZkAesCtrError::vm)?;
|
|
||||||
let keystream = self.alloc_keystream(vm, ranges)?;
|
|
||||||
|
|
||||||
let mut builder = Call::builder(Arc::new(xor(len * 8))).arg(plaintext);
|
|
||||||
for slice in keystream {
|
|
||||||
builder = builder.arg(slice);
|
|
||||||
}
|
|
||||||
let call = builder.build().expect("call should be valid");
|
|
||||||
|
|
||||||
let ciphertext: Vector<U8> = vm.call(call).map_err(ZkAesCtrError::vm)?;
|
|
||||||
|
|
||||||
let mut pos = 0;
|
|
||||||
let plaintext = ReferenceMap::from_iter(ranges.iter_ranges().map(move |range| {
|
|
||||||
let chunk = plaintext
|
|
||||||
.get(pos..pos + range.len())
|
|
||||||
.expect("length was checked");
|
|
||||||
pos += range.len();
|
|
||||||
(range.start, chunk)
|
|
||||||
}));
|
|
||||||
|
|
||||||
let mut pos = 0;
|
|
||||||
let ciphertext = ReferenceMap::from_iter(ranges.iter_ranges().map(move |range| {
|
|
||||||
let chunk = ciphertext
|
|
||||||
.get(pos..pos + range.len())
|
|
||||||
.expect("length was checked");
|
|
||||||
pos += range.len();
|
|
||||||
(range.start, chunk)
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok((plaintext, ciphertext))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_keystream(
|
|
||||||
&mut self,
|
|
||||||
vm: &mut dyn Vm<Binary>,
|
|
||||||
ranges: &RangeSet<usize>,
|
|
||||||
) -> Result<Vec<Vector<U8>>, ZkAesCtrError> {
|
|
||||||
let mut keystream = Vec::new();
|
|
||||||
|
|
||||||
let mut range_iter = ranges.iter_ranges();
|
|
||||||
let mut current_range = range_iter.next();
|
|
||||||
for (pos, record) in self.records.iter_mut() {
|
|
||||||
let pos = *pos;
|
|
||||||
let mut current_block = None;
|
|
||||||
loop {
|
|
||||||
let Some(range) = current_range.take().or_else(|| range_iter.next()) else {
|
|
||||||
return Ok(keystream);
|
|
||||||
};
|
|
||||||
|
|
||||||
if range.start >= record.range.end {
|
|
||||||
current_range = Some(range);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BLOCK_SIZE: usize = 16;
|
|
||||||
let block_num = (range.start - pos) / BLOCK_SIZE;
|
|
||||||
let block = if let Some((current_block_num, block)) = current_block.take()
|
|
||||||
&& current_block_num == block_num
|
|
||||||
{
|
|
||||||
block
|
|
||||||
} else {
|
|
||||||
let block = record.alloc_block(vm, self.key, self.iv, block_num)?;
|
|
||||||
|
|
||||||
current_block = Some((block_num, block));
|
|
||||||
|
|
||||||
block
|
|
||||||
};
|
|
||||||
|
|
||||||
let start = (range.start - pos) % BLOCK_SIZE;
|
|
||||||
let end = (start + range.len()).min(BLOCK_SIZE);
|
|
||||||
let len = end - start;
|
|
||||||
|
|
||||||
keystream.push(block.get(start..end).expect("range is checked"));
|
|
||||||
|
|
||||||
// If the range is larger than a block, process the tail.
|
|
||||||
if range.len() > BLOCK_SIZE {
|
|
||||||
current_range = Some(range.start + len..range.end);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("plaintext length was checked");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct RecordState {
|
|
||||||
explicit_nonce: Option<Vec<u8>>,
|
|
||||||
range: Range<usize>,
|
|
||||||
explicit_nonce_ref: Option<Vector<U8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RecordState {
|
|
||||||
fn alloc_explicit_nonce(
|
|
||||||
&mut self,
|
|
||||||
vm: &mut dyn Vm<Binary>,
|
|
||||||
) -> Result<Vector<U8>, ZkAesCtrError> {
|
|
||||||
if let Some(explicit_nonce) = self.explicit_nonce_ref {
|
|
||||||
Ok(explicit_nonce)
|
|
||||||
} else {
|
|
||||||
const EXPLICIT_NONCE_LEN: usize = 8;
|
|
||||||
let explicit_nonce_ref = vm
|
|
||||||
.alloc_vec::<U8>(EXPLICIT_NONCE_LEN)
|
|
||||||
.map_err(ZkAesCtrError::vm)?;
|
|
||||||
vm.mark_public(explicit_nonce_ref)
|
|
||||||
.map_err(ZkAesCtrError::vm)?;
|
|
||||||
vm.assign(
|
|
||||||
explicit_nonce_ref,
|
|
||||||
self.explicit_nonce
|
|
||||||
.take()
|
|
||||||
.expect("explicit nonce only set once"),
|
|
||||||
)
|
|
||||||
.map_err(ZkAesCtrError::vm)?;
|
|
||||||
vm.commit(explicit_nonce_ref).map_err(ZkAesCtrError::vm)?;
|
|
||||||
|
|
||||||
self.explicit_nonce_ref = Some(explicit_nonce_ref);
|
|
||||||
Ok(explicit_nonce_ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn alloc_block(
|
|
||||||
&mut self,
|
|
||||||
vm: &mut dyn Vm<Binary>,
|
|
||||||
key: Array<U8, 16>,
|
|
||||||
iv: Array<U8, 4>,
|
|
||||||
block: usize,
|
|
||||||
) -> Result<Vector<U8>, ZkAesCtrError> {
|
|
||||||
let explicit_nonce = self.alloc_explicit_nonce(vm)?;
|
|
||||||
let ctr: Array<U8, 4> = vm.alloc().map_err(ZkAesCtrError::vm)?;
|
|
||||||
vm.mark_public(ctr).map_err(ZkAesCtrError::vm)?;
|
|
||||||
const START_CTR: u32 = 2;
|
|
||||||
vm.assign(ctr, (START_CTR + block as u32).to_be_bytes())
|
|
||||||
.map_err(ZkAesCtrError::vm)?;
|
|
||||||
vm.commit(ctr).map_err(ZkAesCtrError::vm)?;
|
|
||||||
|
|
||||||
let block: Array<U8, 16> = vm
|
|
||||||
.call(
|
|
||||||
Call::builder(AES128.clone())
|
|
||||||
.arg(key)
|
|
||||||
.arg(iv)
|
|
||||||
.arg(explicit_nonce)
|
|
||||||
.arg(ctr)
|
|
||||||
.build()
|
|
||||||
.expect("call should be valid"),
|
|
||||||
)
|
|
||||||
.map_err(ZkAesCtrError::vm)?;
|
|
||||||
|
|
||||||
Ok(Vector::from(block))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error for [`ZkAesCtr`].
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error(transparent)]
|
|
||||||
pub(crate) struct ZkAesCtrError(#[from] ErrorRepr);
|
|
||||||
|
|
||||||
impl ZkAesCtrError {
|
|
||||||
fn vm<E>(err: E) -> Self
|
|
||||||
where
|
|
||||||
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
|
|
||||||
{
|
|
||||||
Self(ErrorRepr::Vm(err.into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("zk aes error")]
|
|
||||||
enum ErrorRepr {
|
|
||||||
#[error("vm error: {0}")]
|
|
||||||
Vm(Box<dyn std::error::Error + Send + Sync + 'static>),
|
|
||||||
#[error("transcript bounds exceeded: {len} > {max}")]
|
|
||||||
TranscriptBounds { len: usize, max: usize },
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user