use crate::{
commitment::{
Commitment, CommitmentId, CommitmentInfo, CommitmentKind, CommitmentOpening,
TranscriptCommitments,
},
merkle::MerkleProof,
transcript::get_value_ids,
Direction, EncodingId, RedactedTranscript, SessionHeader, Transcript, TranscriptSlice,
MAX_TOTAL_COMMITTED_DATA,
};
use mpz_circuits::types::ValueType;
use mpz_garble_core::Encoder;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use utils::range::{RangeDisjoint, RangeSet, RangeUnion, ToRangeSet};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SubstringsProofBuilderError {
#[error("invalid commitment id: {0:?}")]
InvalidCommitmentId(CommitmentId),
#[error("missing commitment")]
MissingCommitment,
#[error("commitment {0:?} is not a substrings commitment")]
InvalidCommitmentType(CommitmentId),
#[error("commitment with id {0:?} already exists")]
DuplicateCommitmentId(CommitmentId),
}
pub struct SubstringsProofBuilder<'a> {
commitments: &'a TranscriptCommitments,
transcript_tx: &'a Transcript,
transcript_rx: &'a Transcript,
openings: HashMap<CommitmentId, (CommitmentInfo, CommitmentOpening)>,
}
opaque_debug::implement!(SubstringsProofBuilder<'_>);
impl<'a> SubstringsProofBuilder<'a> {
pub fn new(
commitments: &'a TranscriptCommitments,
transcript_tx: &'a Transcript,
transcript_rx: &'a Transcript,
) -> Self {
Self {
commitments,
transcript_tx,
transcript_rx,
openings: HashMap::default(),
}
}
pub fn commitments(&self) -> &TranscriptCommitments {
self.commitments
}
pub fn reveal_sent(
&mut self,
ranges: &dyn ToRangeSet<usize>,
commitment_kind: CommitmentKind,
) -> Result<&mut Self, SubstringsProofBuilderError> {
self.reveal(ranges, Direction::Sent, commitment_kind)
}
pub fn reveal_recv(
&mut self,
ranges: &dyn ToRangeSet<usize>,
commitment_kind: CommitmentKind,
) -> Result<&mut Self, SubstringsProofBuilderError> {
self.reveal(ranges, Direction::Received, commitment_kind)
}
pub fn reveal(
&mut self,
ranges: &dyn ToRangeSet<usize>,
direction: Direction,
commitment_kind: CommitmentKind,
) -> Result<&mut Self, SubstringsProofBuilderError> {
let com = self
.commitments
.get_id_by_info(commitment_kind, &ranges.to_range_set(), direction)
.ok_or(SubstringsProofBuilderError::MissingCommitment)?;
self.reveal_by_id(com)
}
pub fn reveal_by_id(
&mut self,
id: CommitmentId,
) -> Result<&mut Self, SubstringsProofBuilderError> {
let commitment = self
.commitments()
.get(&id)
.ok_or(SubstringsProofBuilderError::InvalidCommitmentId(id))?;
let info = self
.commitments()
.get_info(&id)
.expect("info exists if commitment exists");
#[allow(irrefutable_let_patterns)]
let Commitment::Blake3(commitment) = commitment
else {
return Err(SubstringsProofBuilderError::InvalidCommitmentType(id));
};
let transcript = match info.direction() {
Direction::Sent => self.transcript_tx,
Direction::Received => self.transcript_rx,
};
let data = transcript.get_bytes_in_ranges(info.ranges());
if self
.openings
.insert(id, (info.clone(), commitment.open(data).into()))
.is_some()
{
return Err(SubstringsProofBuilderError::DuplicateCommitmentId(id));
}
Ok(self)
}
pub fn build(self) -> Result<SubstringsProof, SubstringsProofBuilderError> {
let Self {
commitments,
openings,
..
} = self;
let mut indices = openings
.keys()
.map(|id| id.to_inner() as usize)
.collect::<Vec<_>>();
indices.sort();
let inclusion_proof = commitments.merkle_tree().proof(&indices);
Ok(SubstringsProof {
openings,
inclusion_proof,
})
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SubstringsProofError {
#[error(
"substrings proof opens more data than the maximum allowed: {0} > {}",
MAX_TOTAL_COMMITTED_DATA
)]
MaxDataExceeded(usize),
#[error("proof contains duplicate transcript data")]
DuplicateData(Direction, RangeSet<usize>),
#[error("range of opening {0:?} is out of bounds: {1}")]
RangeOutOfBounds(CommitmentId, usize),
#[error("invalid opening for commitment id: {0:?}")]
InvalidOpening(CommitmentId),
#[error("invalid inclusion proof: {0}")]
InvalidInclusionProof(String),
}
#[derive(Serialize, Deserialize)]
pub struct SubstringsProof {
openings: HashMap<CommitmentId, (CommitmentInfo, CommitmentOpening)>,
inclusion_proof: MerkleProof,
}
opaque_debug::implement!(SubstringsProof);
impl SubstringsProof {
pub fn verify(
self,
header: &SessionHeader,
) -> Result<(RedactedTranscript, RedactedTranscript), SubstringsProofError> {
let Self {
openings,
inclusion_proof,
} = self;
let mut indices = Vec::with_capacity(openings.len());
let mut expected_hashes = Vec::with_capacity(openings.len());
let mut sent = vec![0u8; header.sent_len()];
let mut recv = vec![0u8; header.recv_len()];
let mut sent_ranges = RangeSet::default();
let mut recv_ranges = RangeSet::default();
let mut total_opened = 0u128;
for (id, (info, opening)) in openings {
let CommitmentInfo {
ranges, direction, ..
} = info;
let opened_len = ranges.len();
total_opened += opened_len as u128;
if total_opened > MAX_TOTAL_COMMITTED_DATA as u128 {
return Err(SubstringsProofError::MaxDataExceeded(total_opened as usize));
}
if opening.data().len() != opened_len {
return Err(SubstringsProofError::InvalidOpening(id));
}
match direction {
Direction::Sent => {
if !sent_ranges.is_disjoint(&ranges) {
return Err(SubstringsProofError::DuplicateData(direction, ranges));
}
sent_ranges = sent_ranges.union(&ranges);
}
Direction::Received => {
if !recv_ranges.is_disjoint(&ranges) {
return Err(SubstringsProofError::DuplicateData(direction, ranges));
}
recv_ranges = recv_ranges.union(&ranges);
}
}
let max = ranges
.max()
.ok_or(SubstringsProofError::InvalidOpening(id))?;
let transcript_len = match direction {
Direction::Sent => header.sent_len(),
Direction::Received => header.recv_len(),
};
if max > transcript_len {
return Err(SubstringsProofError::RangeOutOfBounds(id, max));
}
let encodings = get_value_ids(&ranges, direction)
.map(|id| {
header
.encoder()
.encode_by_type(EncodingId::new(&id).to_inner(), &ValueType::U8)
})
.collect::<Vec<_>>();
indices.push(id.to_inner() as usize);
expected_hashes.push(opening.recover(&encodings).hash());
let mut data = opening.into_data();
if data.len() != ranges.len() {
return Err(SubstringsProofError::InvalidOpening(id));
}
let dest = match direction {
Direction::Sent => &mut sent,
Direction::Received => &mut recv,
};
for range in ranges.iter_ranges().rev() {
let start = data.len() - range.len();
dest[range].copy_from_slice(&data[start..]);
data.truncate(start);
}
}
inclusion_proof
.verify(header.merkle_root(), &indices, &expected_hashes)
.map_err(|e| SubstringsProofError::InvalidInclusionProof(e.to_string()))?;
let sent_slices = sent_ranges
.iter_ranges()
.map(|range| TranscriptSlice::new(range.clone(), sent[range].to_vec()))
.collect();
let recv_slices = recv_ranges
.iter_ranges()
.map(|range| TranscriptSlice::new(range.clone(), recv[range].to_vec()))
.collect();
Ok((
RedactedTranscript::new(header.sent_len(), sent_slices),
RedactedTranscript::new(header.recv_len(), recv_slices),
))
}
}