# hash.rs - source
Source of the Rust file `crates/tlsn/src/transcript_internal/commit/hash.rs`.

Skip to main content

#
tlsn/transcript_internal/commit/
hash.rs

    1//! Plaintext hash commitments.
    2
    3use std::collections::HashMap;
    4
    5use mpz_core::bitvec::BitVec;
    6use mpz_hash::{blake3::Blake3, keccak256::Keccak256, sha256::Sha256};
    7use mpz_memory_core::{
    8    DecodeFutureTyped, MemoryExt, Vector,
    9    binary::{Binary, U8},
    10};
    11use mpz_vm_core::{Vm, VmError, prelude::*};
    12use rangeset::set::RangeSet;
    13use tlsn_core::{
    14    hash::{Blinder, Hash, HashAlgId, TypedHash},
    15    transcript::{
    16        Direction,
    17        hash::{PlaintextHash, PlaintextHashSecret},
    18    },
    19};
    20
    21use crate::{Role, transcript_internal::TranscriptRefs};
    22
    23/// Future which will resolve to the committed hash values.
    24#[derive(Debug)]
    25
    26pub(crate) struct HashCommitFuture {
    27    #[allow(clippy::type_complexity)]
    28    futs: Vec<(
    29        Direction,
    30        RangeSet<usize>,
    31        HashAlgId,
    32        DecodeFutureTyped<BitVec, Vec<u8>>,
    33    )>,
    34}
    35
    36impl HashCommitFuture {
    37    /// Tries to receive the value, returning an error if the value is not
    38    /// ready.
    39    pub(crate) fn try_recv(self) -> Result<Vec<PlaintextHash>, HashCommitError> {
    40        let mut output = Vec::new();
    41        for (direction, idx, alg, mut fut) in self.futs {
    42            let hash = fut
    43                .try_recv()
    44                .map_err(|_| HashCommitError::decode())?
    45                .ok_or_else(HashCommitError::decode)?;
    46            output.push(PlaintextHash {
    47                direction,
    48                idx,
    49                hash: TypedHash {
    50                    alg,
    51                    value: Hash::try_from(hash).map_err(HashCommitError::convert)?,
    52                },
    53            });
    54        }
    55
    56        Ok(output)
    57    }
    58}
    59
    60/// Prove plaintext hash commitments.
    61pub(crate) fn prove_hash(
    62    vm: &mut dyn Vm<Binary>,
    63    refs: &TranscriptRefs,
    64    idxs: impl IntoIterator<Item = (Direction, RangeSet<usize>, HashAlgId)>,
    65) -> Result<(HashCommitFuture, Vec<PlaintextHashSecret>), HashCommitError> {
    66    let mut futs = Vec::new();
    67    let mut secrets = Vec::new();
    68    for (direction, idx, alg, hash_ref, blinder_ref) in
    69        hash_commit_inner(vm, Role::Prover, refs, idxs)?
    70    {
    71        let blinder: Blinder = rand::random();
    72
    73        vm.assign(blinder_ref, blinder.as_bytes().to_vec())?;
    74        vm.commit(blinder_ref)?;
    75
    76        let hash_fut = vm.decode(Vector::<U8>::from(hash_ref))?;
    77
    78        futs.push((direction, idx.clone(), alg, hash_fut));
    79        secrets.push(PlaintextHashSecret {
    80            direction,
    81            idx,
    82            blinder,
    83            alg,
    84        });
    85    }
    86
    87    Ok((HashCommitFuture { futs }, secrets))
    88}
    89
    90/// Verify plaintext hash commitments.
    91pub(crate) fn verify_hash(
    92    vm: &mut dyn Vm<Binary>,
    93    refs: &TranscriptRefs,
    94    idxs: impl IntoIterator<Item = (Direction, RangeSet<usize>, HashAlgId)>,
    95) -> Result<HashCommitFuture, HashCommitError> {
    96    let mut futs = Vec::new();
    97    for (direction, idx, alg, hash_ref, blinder_ref) in
    98        hash_commit_inner(vm, Role::Verifier, refs, idxs)?
    99    {
    100        vm.commit(blinder_ref)?;
    101
    102        let hash_fut = vm.decode(Vector::<U8>::from(hash_ref))?;
    103
    104        futs.push((direction, idx, alg, hash_fut));
    105    }
    106
    107    Ok(HashCommitFuture { futs })
    108}
    109
    110#[derive(Clone)]
    111enum Hasher {
    112    Sha256(Sha256),
    113    Blake3(Blake3),
    114    Keccak256(Keccak256),
    115}
    116
    117/// Commit plaintext hashes of the transcript.
    118#[allow(clippy::type_complexity)]
    119fn hash_commit_inner(
    120    vm: &mut dyn Vm<Binary>,
    121    role: Role,
    122    refs: &TranscriptRefs,
    123    idxs: impl IntoIterator<Item = (Direction, RangeSet<usize>, HashAlgId)>,
    124) -> Result<
    125    Vec<(
    126        Direction,
    127        RangeSet<usize>,
    128        HashAlgId,
    129        Array<U8, 32>,
    130        Vector<U8>,
    131    )>,
    132    HashCommitError,
    133> {
    134    let mut output = Vec::new();
    135    let mut hashers = HashMap::new();
    136    for (direction, idx, alg) in idxs {
    137        let blinder = vm.alloc_vec::<U8>(16)?;
    138        match role {
    139            Role::Prover => vm.mark_private(blinder)?,
    140            Role::Verifier => vm.mark_blind(blinder)?,
    141        }
    142
    143        let hash = match alg {
    144            HashAlgId::SHA256 => {
    145                let mut hasher = if let Some(Hasher::Sha256(hasher)) = hashers.get(&alg).cloned() {
    146                    hasher
    147                } else {
    148                    let hasher = Sha256::new_with_init(vm).map_err(HashCommitError::hasher)?;
    149                    hashers.insert(alg, Hasher::Sha256(hasher.clone()));
    150                    hasher
    151                };
    152
    153                let refs = match direction {
    154                    Direction::Sent => &refs.sent,
    155                    Direction::Received => &refs.recv,
    156                };
    157
    158                for range in idx.iter() {
    159                    hasher.update(&refs.get(range).expect("plaintext refs are valid"));
    160                }
    161
    162                hasher.update(&blinder);
    163                hasher.finalize(vm).map_err(HashCommitError::hasher)?
    164            }
    165            HashAlgId::BLAKE3 => {
    166                let mut hasher = if let Some(Hasher::Blake3(hasher)) = hashers.get(&alg).cloned() {
    167                    hasher
    168                } else {
    169                    let hasher = Blake3::new(vm).map_err(HashCommitError::hasher)?;
    170                    hashers.insert(alg, Hasher::Blake3(hasher.clone()));
    171                    hasher
    172                };
    173
    174                let refs = match direction {
    175                    Direction::Sent => &refs.sent,
    176                    Direction::Received => &refs.recv,
    177                };
    178
    179                for range in idx.iter() {
    180                    hasher
    181                        .update(vm, &refs.get(range).expect("plaintext refs are valid"))
    182                        .map_err(HashCommitError::hasher)?;
    183                }
    184                hasher
    185                    .update(vm, &blinder)
    186                    .map_err(HashCommitError::hasher)?;
    187                hasher.finalize(vm).map_err(HashCommitError::hasher)?
    188            }
    189            HashAlgId::KECCAK256 => {
    190                let mut hasher = if let Some(Hasher::Keccak256(hasher)) = hashers.get(&alg).cloned()
    191                {
    192                    hasher
    193                } else {
    194                    let hasher = Keccak256::new_with_init(vm).map_err(HashCommitError::hasher)?;
    195                    hashers.insert(alg, Hasher::Keccak256(hasher.clone()));
    196                    hasher
    197                };
    198
    199                let refs = match direction {
    200                    Direction::Sent => &refs.sent,
    201                    Direction::Received => &refs.recv,
    202                };
    203
    204                for range in idx.iter() {
    205                    hasher
    206                        .update(vm, &refs.get(range).expect("plaintext refs are valid"))
    207                        .map_err(HashCommitError::hasher)?;
    208                }
    209
    210                hasher
    211                    .update(vm, &blinder)
    212                    .map_err(HashCommitError::hasher)?;
    213                hasher.finalize(vm).map_err(HashCommitError::hasher)?
    214            }
    215            alg => {
    216                return Err(HashCommitError::unsupported_alg(alg));
    217            }
    218        };
    219
    220        output.push((direction, idx, alg, hash, blinder));
    221    }
    222
    223    Ok(output)
    224}
    225
    226/// Error type for hash commitments.
    227#[derive(Debug, thiserror::Error)]
    228#[error(transparent)]
    229pub(crate) struct HashCommitError(#[from] ErrorRepr);
    230
    231impl HashCommitError {
    232    fn decode() -> Self {
    233        Self(ErrorRepr::Decode)
    234    }
    235
    236    fn convert(e: &'static str) -> Self {
    237        Self(ErrorRepr::Convert(e))
    238    }
    239
    240    fn hasher<E>(e: E) -> Self
    241    where
    242        E: Into<Box<dyn std::error::Error + Send + Sync>>,
    243    {
    244        Self(ErrorRepr::Hasher(e.into()))
    245    }
    246
    247    fn unsupported_alg(alg: HashAlgId) -> Self {
    248        Self(ErrorRepr::UnsupportedAlg { alg })
    249    }
    250}
    251
    252#[derive(Debug, thiserror::Error)]
    253#[error("hash commit error: {0}")]
    254enum ErrorRepr {
    255    #[error("VM error: {0}")]
    256    Vm(VmError),
    257    #[error("failed to decode hash")]
    258    Decode,
    259    #[error("failed to convert hash: {0}")]
    260    Convert(&'static str),
    261    #[error("unsupported hash algorithm: {alg}")]
    262    UnsupportedAlg { alg: HashAlgId },
    263    #[error("hasher error: {0}")]
    264    Hasher(Box<dyn std::error::Error + Send + Sync>),
    265}
    266
    267impl From<VmError> for HashCommitError {
    268    fn from(value: VmError) -> Self {
    269        Self(ErrorRepr::Vm(value))
    270    }
    271}