mirror of
https://github.com/tlsnotary/tlsn.git
synced 2026-01-09 14:48:13 -05:00
feat: SHA256 transcript commitments (#881)
* feat: SHA256 transcript commitments * clippy
This commit is contained in:
@@ -17,6 +17,7 @@ tlsn-cipher = { workspace = true }
|
||||
mpz-core = { workspace = true }
|
||||
mpz-common = { workspace = true }
|
||||
mpz-memory-core = { workspace = true }
|
||||
mpz-hash = { workspace = true }
|
||||
mpz-vm-core = { workspace = true }
|
||||
mpz-zk = { workspace = true }
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Plaintext commitment and proof of encryption.
|
||||
|
||||
pub mod hash;
|
||||
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_memory_core::{binary::Binary, DecodeFutureTyped};
|
||||
use mpz_vm_core::{prelude::*, Vm};
|
||||
|
||||
197
crates/common/src/commit/hash.rs
Normal file
197
crates/common/src/commit/hash.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Plaintext hash commitments.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mpz_core::bitvec::BitVec;
|
||||
use mpz_hash::sha256::Sha256;
|
||||
use mpz_memory_core::{
|
||||
binary::{Binary, U8},
|
||||
DecodeFutureTyped, MemoryExt, Vector,
|
||||
};
|
||||
use mpz_vm_core::{prelude::*, Vm, VmError};
|
||||
use tlsn_core::{
|
||||
hash::{Blinder, Hash, HashAlgId, TypedHash},
|
||||
transcript::{
|
||||
hash::{PlaintextHash, PlaintextHashSecret},
|
||||
Direction, Idx,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{transcript::TranscriptRefs, Role};
|
||||
|
||||
/// Future which will resolve to the committed hash values.
|
||||
#[derive(Debug)]
|
||||
pub struct HashCommitFuture {
|
||||
#[allow(clippy::type_complexity)]
|
||||
futs: Vec<(
|
||||
Direction,
|
||||
Idx,
|
||||
HashAlgId,
|
||||
DecodeFutureTyped<BitVec, Vec<u8>>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl HashCommitFuture {
|
||||
/// Tries to receive the value, returning an error if the value is not
|
||||
/// ready.
|
||||
pub fn try_recv(self) -> Result<Vec<PlaintextHash>, HashCommitError> {
|
||||
let mut output = Vec::new();
|
||||
for (direction, idx, alg, mut fut) in self.futs {
|
||||
let hash = fut
|
||||
.try_recv()
|
||||
.map_err(|_| HashCommitError::decode())?
|
||||
.ok_or_else(HashCommitError::decode)?;
|
||||
output.push(PlaintextHash {
|
||||
direction,
|
||||
idx,
|
||||
hash: TypedHash {
|
||||
alg,
|
||||
value: Hash::try_from(hash).map_err(HashCommitError::convert)?,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prove plaintext hash commitments.
|
||||
pub fn prove_hash(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
refs: &TranscriptRefs,
|
||||
idxs: impl IntoIterator<Item = (Direction, Idx, HashAlgId)>,
|
||||
) -> Result<(HashCommitFuture, Vec<PlaintextHashSecret>), HashCommitError> {
|
||||
let mut futs = Vec::new();
|
||||
let mut secrets = Vec::new();
|
||||
for (direction, idx, alg, hash_ref, blinder_ref) in
|
||||
hash_commit_inner(vm, Role::Prover, refs, idxs)?
|
||||
{
|
||||
let blinder: Blinder = rand::random();
|
||||
|
||||
vm.assign(blinder_ref, blinder.as_bytes().to_vec())?;
|
||||
vm.commit(blinder_ref)?;
|
||||
|
||||
let hash_fut = vm.decode(Vector::<U8>::from(hash_ref))?;
|
||||
|
||||
futs.push((direction, idx.clone(), alg, hash_fut));
|
||||
secrets.push(PlaintextHashSecret {
|
||||
direction,
|
||||
idx,
|
||||
blinder,
|
||||
alg,
|
||||
});
|
||||
}
|
||||
|
||||
Ok((HashCommitFuture { futs }, secrets))
|
||||
}
|
||||
|
||||
/// Verify plaintext hash commitments.
|
||||
pub fn verify_hash(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
refs: &TranscriptRefs,
|
||||
idxs: impl IntoIterator<Item = (Direction, Idx, HashAlgId)>,
|
||||
) -> Result<HashCommitFuture, HashCommitError> {
|
||||
let mut futs = Vec::new();
|
||||
for (direction, idx, alg, hash_ref, blinder_ref) in
|
||||
hash_commit_inner(vm, Role::Verifier, refs, idxs)?
|
||||
{
|
||||
vm.commit(blinder_ref)?;
|
||||
|
||||
let hash_fut = vm.decode(Vector::<U8>::from(hash_ref))?;
|
||||
|
||||
futs.push((direction, idx, alg, hash_fut));
|
||||
}
|
||||
|
||||
Ok(HashCommitFuture { futs })
|
||||
}
|
||||
|
||||
/// Commit plaintext hashes of the transcript.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn hash_commit_inner(
|
||||
vm: &mut dyn Vm<Binary>,
|
||||
role: Role,
|
||||
refs: &TranscriptRefs,
|
||||
idxs: impl IntoIterator<Item = (Direction, Idx, HashAlgId)>,
|
||||
) -> Result<Vec<(Direction, Idx, HashAlgId, Array<U8, 32>, Vector<U8>)>, HashCommitError> {
|
||||
let mut output = Vec::new();
|
||||
let mut hashers = HashMap::new();
|
||||
for (direction, idx, alg) in idxs {
|
||||
let blinder = vm.alloc_vec::<U8>(16)?;
|
||||
match role {
|
||||
Role::Prover => vm.mark_private(blinder)?,
|
||||
Role::Verifier => vm.mark_blind(blinder)?,
|
||||
}
|
||||
|
||||
let hash = match alg {
|
||||
HashAlgId::SHA256 => {
|
||||
let mut hasher = if let Some(hasher) = hashers.get(&alg).cloned() {
|
||||
hasher
|
||||
} else {
|
||||
let hasher = Sha256::new_with_init(vm).map_err(HashCommitError::hasher)?;
|
||||
hashers.insert(alg, hasher.clone());
|
||||
hasher
|
||||
};
|
||||
|
||||
for plaintext in refs.get(direction, &idx).expect("plaintext refs are valid") {
|
||||
hasher.update(&plaintext);
|
||||
}
|
||||
hasher.update(&blinder);
|
||||
hasher.finalize(vm).map_err(HashCommitError::hasher)?
|
||||
}
|
||||
alg => {
|
||||
return Err(HashCommitError::unsupported_alg(alg));
|
||||
}
|
||||
};
|
||||
|
||||
output.push((direction, idx, alg, hash, blinder));
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Error type for hash commitments.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error(transparent)]
|
||||
pub struct HashCommitError(#[from] ErrorRepr);
|
||||
|
||||
impl HashCommitError {
|
||||
fn decode() -> Self {
|
||||
Self(ErrorRepr::Decode)
|
||||
}
|
||||
|
||||
fn convert(e: &'static str) -> Self {
|
||||
Self(ErrorRepr::Convert(e))
|
||||
}
|
||||
|
||||
fn hasher<E>(e: E) -> Self
|
||||
where
|
||||
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
Self(ErrorRepr::Hasher(e.into()))
|
||||
}
|
||||
|
||||
fn unsupported_alg(alg: HashAlgId) -> Self {
|
||||
Self(ErrorRepr::UnsupportedAlg { alg })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("hash commit error: {0}")]
|
||||
enum ErrorRepr {
|
||||
#[error("VM error: {0}")]
|
||||
Vm(VmError),
|
||||
#[error("failed to decode hash")]
|
||||
Decode,
|
||||
#[error("failed to convert hash: {0}")]
|
||||
Convert(&'static str),
|
||||
#[error("unsupported hash algorithm: {alg}")]
|
||||
UnsupportedAlg { alg: HashAlgId },
|
||||
#[error("hasher error: {0}")]
|
||||
Hasher(Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
|
||||
impl From<VmError> for HashCommitError {
|
||||
fn from(value: VmError) -> Self {
|
||||
Self(ErrorRepr::Vm(value))
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ impl Display for HashAlgId {
|
||||
}
|
||||
|
||||
/// A typed hash value.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct TypedHash {
|
||||
/// The algorithm of the hash.
|
||||
pub alg: HashAlgId,
|
||||
@@ -109,7 +109,7 @@ pub struct TypedHash {
|
||||
}
|
||||
|
||||
/// A hash value.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct Hash {
|
||||
// To avoid heap allocation, we use a fixed-size array.
|
||||
// 64 bytes should be sufficient for most hash algorithms.
|
||||
@@ -253,12 +253,13 @@ impl<T: HashAlgorithm + ?Sized> HashAlgorithmExt for T {}
|
||||
|
||||
/// A hash blinder.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct Blinder([u8; 16]);
|
||||
pub struct Blinder([u8; 16]);
|
||||
|
||||
opaque_debug::implement!(Blinder);
|
||||
|
||||
impl Blinder {
|
||||
pub(crate) fn as_bytes(&self) -> &[u8] {
|
||||
/// Returns the blinder as a byte slice.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,17 +35,6 @@ impl Secrets {
|
||||
|
||||
/// Returns a transcript proof builder.
|
||||
pub fn transcript_proof_builder(&self) -> TranscriptProofBuilder<'_> {
|
||||
let encoding_secret = self
|
||||
.transcript_commitment_secrets
|
||||
.iter()
|
||||
.find_map(|secret| {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let TranscriptSecret::Encoding(secret) = secret {
|
||||
Some(secret)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
TranscriptProofBuilder::new(&self.transcript, encoding_secret)
|
||||
TranscriptProofBuilder::new(&self.transcript, &self.transcript_commitment_secrets)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
mod commit;
|
||||
#[doc(hidden)]
|
||||
pub mod encoding;
|
||||
pub mod hash;
|
||||
mod proof;
|
||||
|
||||
use std::{fmt, ops::Range};
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::{
|
||||
hash::{impl_domain_separator, HashAlgId},
|
||||
transcript::{
|
||||
encoding::{EncodingCommitment, EncodingTree},
|
||||
hash::{PlaintextHash, PlaintextHashSecret},
|
||||
Direction, Idx, Transcript,
|
||||
},
|
||||
};
|
||||
@@ -50,6 +51,8 @@ impl fmt::Display for TranscriptCommitmentKind {
|
||||
pub enum TranscriptCommitment {
|
||||
/// Encoding commitment.
|
||||
Encoding(EncodingCommitment),
|
||||
/// Plaintext hash commitment.
|
||||
Hash(PlaintextHash),
|
||||
}
|
||||
|
||||
impl_domain_separator!(TranscriptCommitment);
|
||||
@@ -60,6 +63,8 @@ impl_domain_separator!(TranscriptCommitment);
|
||||
pub enum TranscriptSecret {
|
||||
/// Encoding tree.
|
||||
Encoding(EncodingTree),
|
||||
/// Plaintext hash secret.
|
||||
Hash(PlaintextHashSecret),
|
||||
}
|
||||
|
||||
impl_domain_separator!(TranscriptSecret);
|
||||
@@ -69,6 +74,7 @@ impl_domain_separator!(TranscriptSecret);
|
||||
pub struct TranscriptCommitConfig {
|
||||
encoding_hash_alg: HashAlgId,
|
||||
has_encoding: bool,
|
||||
has_hash: bool,
|
||||
commits: Vec<((Direction, Idx), TranscriptCommitmentKind)>,
|
||||
}
|
||||
|
||||
@@ -83,11 +89,16 @@ impl TranscriptCommitConfig {
|
||||
&self.encoding_hash_alg
|
||||
}
|
||||
|
||||
/// Returns whether the configuration has any encoding commitments.
|
||||
/// Returns `true` if the configuration has any encoding commitments.
|
||||
pub fn has_encoding(&self) -> bool {
|
||||
self.has_encoding
|
||||
}
|
||||
|
||||
/// Returns `true` if the configuration has any hash commitments.
|
||||
pub fn has_hash(&self) -> bool {
|
||||
self.has_hash
|
||||
}
|
||||
|
||||
/// Returns an iterator over the encoding commitment indices.
|
||||
pub fn iter_encoding(&self) -> impl Iterator<Item = &(Direction, Idx)> {
|
||||
self.commits.iter().filter_map(|(idx, kind)| match kind {
|
||||
@@ -108,6 +119,10 @@ impl TranscriptCommitConfig {
|
||||
pub fn to_request(&self) -> TranscriptCommitRequest {
|
||||
TranscriptCommitRequest {
|
||||
encoding: self.has_encoding,
|
||||
hash: self
|
||||
.iter_hash()
|
||||
.map(|((dir, idx), alg)| (*dir, idx.clone(), *alg))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,6 +136,7 @@ pub struct TranscriptCommitConfigBuilder<'a> {
|
||||
transcript: &'a Transcript,
|
||||
encoding_hash_alg: HashAlgId,
|
||||
has_encoding: bool,
|
||||
has_hash: bool,
|
||||
default_kind: TranscriptCommitmentKind,
|
||||
commits: HashSet<((Direction, Idx), TranscriptCommitmentKind)>,
|
||||
}
|
||||
@@ -132,6 +148,7 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
transcript,
|
||||
encoding_hash_alg: HashAlgId::BLAKE3,
|
||||
has_encoding: false,
|
||||
has_hash: false,
|
||||
default_kind: TranscriptCommitmentKind::Encoding,
|
||||
commits: HashSet::default(),
|
||||
}
|
||||
@@ -176,8 +193,9 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
));
|
||||
}
|
||||
|
||||
if let TranscriptCommitmentKind::Encoding = kind {
|
||||
self.has_encoding = true;
|
||||
match kind {
|
||||
TranscriptCommitmentKind::Encoding => self.has_encoding = true,
|
||||
TranscriptCommitmentKind::Hash { .. } => self.has_hash = true,
|
||||
}
|
||||
|
||||
self.commits.insert(((direction, idx), kind));
|
||||
@@ -228,6 +246,7 @@ impl<'a> TranscriptCommitConfigBuilder<'a> {
|
||||
Ok(TranscriptCommitConfig {
|
||||
encoding_hash_alg: self.encoding_hash_alg,
|
||||
has_encoding: self.has_encoding,
|
||||
has_hash: self.has_hash,
|
||||
commits: Vec::from_iter(self.commits),
|
||||
})
|
||||
}
|
||||
@@ -275,6 +294,7 @@ impl fmt::Display for TranscriptCommitConfigBuilderError {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TranscriptCommitRequest {
|
||||
encoding: bool,
|
||||
hash: Vec<(Direction, Idx, HashAlgId)>,
|
||||
}
|
||||
|
||||
impl TranscriptCommitRequest {
|
||||
@@ -282,6 +302,16 @@ impl TranscriptCommitRequest {
|
||||
pub fn encoding(&self) -> bool {
|
||||
self.encoding
|
||||
}
|
||||
|
||||
/// Returns `true` if a hash commitment is requested.
|
||||
pub fn has_hash(&self) -> bool {
|
||||
!self.hash.is_empty()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the hash commitments.
|
||||
pub fn iter_hash(&self) -> impl Iterator<Item = &(Direction, Idx, HashAlgId)> {
|
||||
self.hash.iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
46
crates/core/src/transcript/hash.rs
Normal file
46
crates/core/src/transcript/hash.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! Plaintext hash commitments.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
hash::{impl_domain_separator, Blinder, HashAlgId, HashAlgorithm, TypedHash},
|
||||
transcript::{Direction, Idx},
|
||||
};
|
||||
|
||||
/// Hashes plaintext with a blinder.
|
||||
///
|
||||
/// By convention, plaintext is hashed as `H(msg | blinder)`.
|
||||
pub fn hash_plaintext(hasher: &dyn HashAlgorithm, msg: &[u8], blinder: &Blinder) -> TypedHash {
|
||||
TypedHash {
|
||||
alg: hasher.id(),
|
||||
value: hasher.hash_prefixed(msg, blinder.as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Hash of plaintext in the transcript.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct PlaintextHash {
|
||||
/// Direction of the plaintext.
|
||||
pub direction: Direction,
|
||||
/// Index of plaintext.
|
||||
pub idx: Idx,
|
||||
/// The hash of the data.
|
||||
pub hash: TypedHash,
|
||||
}
|
||||
|
||||
impl_domain_separator!(PlaintextHash);
|
||||
|
||||
/// Secret component of [`PlaintextHash`].
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct PlaintextHashSecret {
|
||||
/// Direction of the plaintext.
|
||||
pub direction: Direction,
|
||||
/// Index of plaintext.
|
||||
pub idx: Idx,
|
||||
/// The algorithm of the hash.
|
||||
pub alg: HashAlgId,
|
||||
/// Blinder for the hash.
|
||||
pub blinder: Blinder,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(PlaintextHashSecret);
|
||||
@@ -6,23 +6,31 @@ use std::{collections::HashSet, fmt};
|
||||
|
||||
use crate::{
|
||||
connection::TranscriptLength,
|
||||
hash::HashAlgId,
|
||||
transcript::{
|
||||
commit::{TranscriptCommitment, TranscriptCommitmentKind},
|
||||
encoding::{EncodingProof, EncodingProofError, EncodingTree},
|
||||
Direction, Idx, PartialTranscript, Transcript,
|
||||
hash::{hash_plaintext, PlaintextHash, PlaintextHashSecret},
|
||||
Direction, Idx, PartialTranscript, Transcript, TranscriptSecret,
|
||||
},
|
||||
CryptoProvider,
|
||||
};
|
||||
|
||||
/// Default commitment kinds in order of preference for building transcript
|
||||
/// proofs.
|
||||
const DEFAULT_COMMITMENT_KINDS: &[TranscriptCommitmentKind] = &[TranscriptCommitmentKind::Encoding];
|
||||
const DEFAULT_COMMITMENT_KINDS: &[TranscriptCommitmentKind] = &[
|
||||
TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::SHA256,
|
||||
},
|
||||
TranscriptCommitmentKind::Encoding,
|
||||
];
|
||||
|
||||
/// Proof of the contents of a transcript.
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct TranscriptProof {
|
||||
transcript: PartialTranscript,
|
||||
encoding_proof: Option<EncodingProof>,
|
||||
hash_secrets: Vec<PlaintextHashSecret>,
|
||||
}
|
||||
|
||||
opaque_debug::implement!(TranscriptProof);
|
||||
@@ -42,7 +50,24 @@ impl TranscriptProof {
|
||||
length: &TranscriptLength,
|
||||
commitments: impl IntoIterator<Item = &'a TranscriptCommitment>,
|
||||
) -> Result<PartialTranscript, TranscriptProofError> {
|
||||
let commitments: Vec<_> = commitments.into_iter().collect();
|
||||
let mut encoding_commitment = None;
|
||||
let mut hash_commitments = HashSet::new();
|
||||
// Index commitments.
|
||||
for commitment in commitments {
|
||||
match commitment {
|
||||
TranscriptCommitment::Encoding(commitment) => {
|
||||
if encoding_commitment.replace(commitment).is_some() {
|
||||
return Err(TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
"multiple encoding commitments are present.",
|
||||
));
|
||||
}
|
||||
}
|
||||
TranscriptCommitment::Hash(plaintext_hash) => {
|
||||
hash_commitments.insert(plaintext_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.transcript.sent_unsafe().len() != length.sent as usize
|
||||
|| self.transcript.received_unsafe().len() != length.received as usize
|
||||
@@ -58,22 +83,12 @@ impl TranscriptProof {
|
||||
|
||||
// Verify encoding proof.
|
||||
if let Some(proof) = self.encoding_proof {
|
||||
let commitment = commitments
|
||||
.iter()
|
||||
.find_map(|commitment| {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let TranscriptCommitment::Encoding(encoding) = commitment {
|
||||
Some(encoding)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
"contains an encoding proof but attestation is missing encoding commitment",
|
||||
)
|
||||
})?;
|
||||
let commitment = encoding_commitment.ok_or_else(|| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Encoding,
|
||||
"contains an encoding proof but missing encoding commitment",
|
||||
)
|
||||
})?;
|
||||
|
||||
let (auth_sent, auth_recv) = proof.verify_with_provider(
|
||||
provider,
|
||||
@@ -86,7 +101,53 @@ impl TranscriptProof {
|
||||
total_auth_recv.union_mut(&auth_recv);
|
||||
}
|
||||
|
||||
// TODO: Support hash openings.
|
||||
let mut buffer = Vec::new();
|
||||
for PlaintextHashSecret {
|
||||
direction,
|
||||
idx,
|
||||
alg,
|
||||
blinder,
|
||||
} in self.hash_secrets
|
||||
{
|
||||
let hasher = provider.hash.get(&alg).map_err(|_| {
|
||||
TranscriptProofError::new(
|
||||
ErrorKind::Hash,
|
||||
format!("hash opening has unknown algorithm: {alg}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
let (plaintext, auth) = match direction {
|
||||
Direction::Sent => (self.transcript.sent_unsafe(), &mut total_auth_sent),
|
||||
Direction::Received => (self.transcript.received_unsafe(), &mut total_auth_recv),
|
||||
};
|
||||
|
||||
if idx.end() > plaintext.len() {
|
||||
return Err(TranscriptProofError::new(
|
||||
ErrorKind::Hash,
|
||||
"hash opening index is out of bounds",
|
||||
));
|
||||
}
|
||||
|
||||
buffer.clear();
|
||||
for range in idx.iter_ranges() {
|
||||
buffer.extend_from_slice(&plaintext[range]);
|
||||
}
|
||||
|
||||
let expected = PlaintextHash {
|
||||
direction,
|
||||
idx,
|
||||
hash: hash_plaintext(hasher, &buffer, &blinder),
|
||||
};
|
||||
|
||||
if !hash_commitments.contains(&expected) {
|
||||
return Err(TranscriptProofError::new(
|
||||
ErrorKind::Hash,
|
||||
"hash opening does not match any commitment",
|
||||
));
|
||||
}
|
||||
|
||||
auth.union_mut(&expected.idx);
|
||||
}
|
||||
|
||||
// Assert that all the authenticated data are covered by the proof.
|
||||
if &total_auth_sent != self.transcript.sent_authed()
|
||||
@@ -124,7 +185,6 @@ impl TranscriptProofError {
|
||||
#[derive(Debug)]
|
||||
enum ErrorKind {
|
||||
Encoding,
|
||||
#[allow(dead_code)]
|
||||
Hash,
|
||||
Proof,
|
||||
}
|
||||
@@ -153,34 +213,6 @@ impl From<EncodingProofError> for TranscriptProofError {
|
||||
}
|
||||
}
|
||||
|
||||
/// Union of committed ranges of all commitment kinds.
|
||||
#[derive(Debug)]
|
||||
struct CommittedIdx {
|
||||
sent: Idx,
|
||||
recv: Idx,
|
||||
}
|
||||
|
||||
impl CommittedIdx {
|
||||
fn new(encoding_tree: Option<&EncodingTree>) -> Self {
|
||||
let mut sent = Idx::default();
|
||||
let mut recv = Idx::default();
|
||||
|
||||
if let Some(tree) = encoding_tree {
|
||||
sent.union_mut(tree.idx(Direction::Sent));
|
||||
recv.union_mut(tree.idx(Direction::Received));
|
||||
}
|
||||
|
||||
Self { sent, recv }
|
||||
}
|
||||
|
||||
fn idx(&self, direction: &Direction) -> &Idx {
|
||||
match direction {
|
||||
Direction::Sent => &self.sent,
|
||||
Direction::Received => &self.recv,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Union of ranges to reveal.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct QueryIdx {
|
||||
@@ -221,18 +253,47 @@ pub struct TranscriptProofBuilder<'a> {
|
||||
commitment_kinds: Vec<TranscriptCommitmentKind>,
|
||||
transcript: &'a Transcript,
|
||||
encoding_tree: Option<&'a EncodingTree>,
|
||||
committed_idx: CommittedIdx,
|
||||
hash_secrets: Vec<&'a PlaintextHashSecret>,
|
||||
committed_sent: Idx,
|
||||
committed_recv: Idx,
|
||||
query_idx: QueryIdx,
|
||||
}
|
||||
|
||||
impl<'a> TranscriptProofBuilder<'a> {
|
||||
/// Creates a new proof config builder.
|
||||
pub(crate) fn new(transcript: &'a Transcript, encoding_tree: Option<&'a EncodingTree>) -> Self {
|
||||
/// Creates a new proof builder.
|
||||
pub(crate) fn new(
|
||||
transcript: &'a Transcript,
|
||||
secrets: impl IntoIterator<Item = &'a TranscriptSecret>,
|
||||
) -> Self {
|
||||
let mut committed_sent = Idx::empty();
|
||||
let mut committed_recv = Idx::empty();
|
||||
|
||||
let mut encoding_tree = None;
|
||||
let mut hash_secrets = Vec::new();
|
||||
for secret in secrets {
|
||||
match secret {
|
||||
TranscriptSecret::Encoding(tree) => {
|
||||
committed_sent.union_mut(tree.idx(Direction::Sent));
|
||||
committed_recv.union_mut(tree.idx(Direction::Received));
|
||||
encoding_tree = Some(tree);
|
||||
}
|
||||
TranscriptSecret::Hash(hash) => {
|
||||
match hash.direction {
|
||||
Direction::Sent => committed_sent.union_mut(&hash.idx),
|
||||
Direction::Received => committed_recv.union_mut(&hash.idx),
|
||||
}
|
||||
hash_secrets.push(hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
commitment_kinds: DEFAULT_COMMITMENT_KINDS.to_vec(),
|
||||
transcript,
|
||||
encoding_tree,
|
||||
committed_idx: CommittedIdx::new(encoding_tree),
|
||||
hash_secrets,
|
||||
committed_sent,
|
||||
committed_recv,
|
||||
query_idx: QueryIdx::new(),
|
||||
}
|
||||
}
|
||||
@@ -277,10 +338,15 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
));
|
||||
}
|
||||
|
||||
if idx.is_subset(self.committed_idx.idx(&direction)) {
|
||||
let committed = match direction {
|
||||
Direction::Sent => &self.committed_sent,
|
||||
Direction::Received => &self.committed_recv,
|
||||
};
|
||||
|
||||
if idx.is_subset(committed) {
|
||||
self.query_idx.union(&direction, &idx);
|
||||
} else {
|
||||
let missing = idx.difference(self.committed_idx.idx(&direction));
|
||||
let missing = idx.difference(committed);
|
||||
return Err(TranscriptProofBuilderError::new(
|
||||
BuilderErrorKind::MissingCommitment,
|
||||
format!("commitment is missing for ranges in {direction} transcript: {missing}"),
|
||||
@@ -320,6 +386,7 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
.transcript
|
||||
.to_partial(self.query_idx.sent.clone(), self.query_idx.recv.clone()),
|
||||
encoding_proof: None,
|
||||
hash_secrets: Vec::new(),
|
||||
};
|
||||
let mut uncovered_query_idx = self.query_idx.clone();
|
||||
let mut commitment_kinds_iter = self.commitment_kinds.iter();
|
||||
@@ -372,6 +439,39 @@ impl<'a> TranscriptProofBuilder<'a> {
|
||||
);
|
||||
}
|
||||
}
|
||||
TranscriptCommitmentKind::Hash { alg } => {
|
||||
let (sent_hashes, sent_uncovered) =
|
||||
uncovered_query_idx.sent.as_range_set().cover_by(
|
||||
self.hash_secrets.iter().filter(|hash| {
|
||||
hash.direction == Direction::Sent && &hash.alg == alg
|
||||
}),
|
||||
|hash| &hash.idx.0,
|
||||
);
|
||||
// Uncovered ranges will be checked with ranges of the next
|
||||
// preferred commitment kind.
|
||||
uncovered_query_idx.sent = Idx(sent_uncovered);
|
||||
|
||||
let (recv_hashes, recv_uncovered) =
|
||||
uncovered_query_idx.recv.as_range_set().cover_by(
|
||||
self.hash_secrets.iter().filter(|hash| {
|
||||
hash.direction == Direction::Received && &hash.alg == alg
|
||||
}),
|
||||
|hash| &hash.idx.0,
|
||||
);
|
||||
uncovered_query_idx.recv = Idx(recv_uncovered);
|
||||
|
||||
transcript_proof.hash_secrets.extend(
|
||||
sent_hashes
|
||||
.into_iter()
|
||||
.map(|s| PlaintextHashSecret::clone(s)),
|
||||
);
|
||||
transcript_proof.hash_secrets.extend(
|
||||
recv_hashes
|
||||
.into_iter()
|
||||
.map(|s| PlaintextHashSecret::clone(s)),
|
||||
);
|
||||
}
|
||||
#[allow(unreachable_patterns)]
|
||||
kind => {
|
||||
return Err(TranscriptProofBuilderError::new(
|
||||
BuilderErrorKind::NotSupported,
|
||||
@@ -463,13 +563,14 @@ impl fmt::Display for TranscriptProofBuilderError {
|
||||
#[allow(clippy::single_range_in_vec_init)]
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rangeset::RangeSet;
|
||||
use rstest::rstest;
|
||||
use tlsn_data_fixtures::http::{request::GET_WITH_HEADER, response::OK_JSON};
|
||||
|
||||
use crate::{
|
||||
fixtures::{encoding_provider, request_fixture, ConnectionFixture, RequestFixture},
|
||||
hash::{Blake3, HashAlgId},
|
||||
hash::{Blake3, Blinder, HashAlgId},
|
||||
transcript::TranscriptCommitConfigBuilder,
|
||||
};
|
||||
|
||||
@@ -488,7 +589,8 @@ mod tests {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree));
|
||||
let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
|
||||
|
||||
builder.reveal_recv(&(0..transcript.len().1)).unwrap();
|
||||
|
||||
@@ -509,7 +611,7 @@ mod tests {
|
||||
[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 mut builder = TranscriptProofBuilder::new(&transcript, None);
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
|
||||
|
||||
let err = builder.reveal(&(10..15), Direction::Sent).unwrap_err();
|
||||
assert!(matches!(err.kind, BuilderErrorKind::Index));
|
||||
@@ -527,16 +629,106 @@ mod tests {
|
||||
[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 mut builder = TranscriptProofBuilder::new(&transcript, None);
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
|
||||
|
||||
let err = builder.reveal_recv(&(9..11)).unwrap_err();
|
||||
assert!(matches!(err.kind, BuilderErrorKind::MissingCommitment));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_reveal_with_hash_commitment() {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||
let provider = CryptoProvider::default();
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
|
||||
let direction = Direction::Sent;
|
||||
let idx = Idx::new(0..10);
|
||||
let blinder: Blinder = rng.random();
|
||||
let alg = HashAlgId::SHA256;
|
||||
let hasher = provider.hash.get(&alg).unwrap();
|
||||
|
||||
let commitment = PlaintextHash {
|
||||
direction,
|
||||
idx: idx.clone(),
|
||||
hash: hash_plaintext(hasher, &transcript.sent()[0..10], &blinder),
|
||||
};
|
||||
|
||||
let secret = PlaintextHashSecret {
|
||||
direction,
|
||||
idx: idx.clone(),
|
||||
alg,
|
||||
blinder,
|
||||
};
|
||||
|
||||
let secrets = vec![TranscriptSecret::Hash(secret)];
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
|
||||
|
||||
builder.reveal_sent(&(0..10)).unwrap();
|
||||
|
||||
let transcript_proof = builder.build().unwrap();
|
||||
|
||||
let partial_transcript = transcript_proof
|
||||
.verify_with_provider(
|
||||
&provider,
|
||||
&transcript.length(),
|
||||
&[TranscriptCommitment::Hash(commitment)],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
partial_transcript.sent_unsafe()[0..10],
|
||||
transcript.sent()[0..10]
|
||||
);
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_reveal_with_inconsistent_hash_commitment() {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
|
||||
let provider = CryptoProvider::default();
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
|
||||
let direction = Direction::Sent;
|
||||
let idx = Idx::new(0..10);
|
||||
let blinder: Blinder = rng.random();
|
||||
let alg = HashAlgId::SHA256;
|
||||
let hasher = provider.hash.get(&alg).unwrap();
|
||||
|
||||
let commitment = PlaintextHash {
|
||||
direction,
|
||||
idx: idx.clone(),
|
||||
hash: hash_plaintext(hasher, &transcript.sent()[0..10], &blinder),
|
||||
};
|
||||
|
||||
let secret = PlaintextHashSecret {
|
||||
direction,
|
||||
idx: idx.clone(),
|
||||
alg,
|
||||
// Use a different blinder to create an inconsistent commitment
|
||||
blinder: rng.random(),
|
||||
};
|
||||
|
||||
let secrets = vec![TranscriptSecret::Hash(secret)];
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
|
||||
|
||||
builder.reveal_sent(&(0..10)).unwrap();
|
||||
|
||||
let transcript_proof = builder.build().unwrap();
|
||||
|
||||
let err = transcript_proof
|
||||
.verify_with_provider(
|
||||
&provider,
|
||||
&transcript.length(),
|
||||
&[TranscriptCommitment::Hash(commitment)],
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err.kind, ErrorKind::Hash));
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn test_set_commitment_kinds_with_duplicates() {
|
||||
let transcript = Transcript::new(GET_WITH_HEADER, OK_JSON);
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, None);
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &[]);
|
||||
builder.commitment_kinds(&[
|
||||
TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::SHA256,
|
||||
@@ -621,7 +813,8 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree));
|
||||
let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
|
||||
|
||||
if success {
|
||||
assert!(builder.reveal_recv(&reveal_recv_rangeset).is_ok());
|
||||
@@ -693,7 +886,8 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, Some(&encoding_tree));
|
||||
let secrets = vec![TranscriptSecret::Encoding(encoding_tree)];
|
||||
let mut builder = TranscriptProofBuilder::new(&transcript, &secrets);
|
||||
builder.reveal_sent(&reveal_sent_rangeset).unwrap();
|
||||
builder.reveal_recv(&reveal_recv_rangeset).unwrap();
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use tls_client::{ClientConnection, ServerName as TlsServerName};
|
||||
use tls_client_async::{bind_client, TlsConnection};
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_common::{
|
||||
commit::commit_records,
|
||||
commit::{commit_records, hash::prove_hash},
|
||||
context::build_mt_context,
|
||||
encoding,
|
||||
mux::attach_mux,
|
||||
@@ -43,7 +43,7 @@ use tlsn_core::{
|
||||
TranscriptLength,
|
||||
},
|
||||
request::{Request, RequestConfig},
|
||||
transcript::{Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
transcript::{Direction, Transcript, TranscriptCommitment, TranscriptSecret},
|
||||
ProvePayload, Secrets,
|
||||
};
|
||||
use tlsn_deap::Deap;
|
||||
@@ -376,6 +376,7 @@ impl Prover<state::Committed> {
|
||||
.map_err(ProverError::zk)?;
|
||||
}
|
||||
|
||||
let mut hash_commitments = None;
|
||||
if let Some(commit_config) = config.transcript_commit() {
|
||||
if commit_config.has_encoding() {
|
||||
let hasher = self
|
||||
@@ -406,13 +407,36 @@ impl Prover<state::Committed> {
|
||||
.push(TranscriptSecret::Encoding(tree));
|
||||
}
|
||||
|
||||
// TODO: Other commitment types.
|
||||
if commit_config.has_hash() {
|
||||
hash_commitments = Some(
|
||||
prove_hash(
|
||||
vm,
|
||||
transcript_refs,
|
||||
commit_config
|
||||
.iter_hash()
|
||||
.map(|((dir, idx), alg)| (*dir, idx.clone(), *alg)),
|
||||
)
|
||||
.map_err(ProverError::commit)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mux_fut
|
||||
.poll_with(vm.execute_all(ctx).map_err(ProverError::zk))
|
||||
.await?;
|
||||
|
||||
if let Some((hash_fut, hash_secrets)) = hash_commitments {
|
||||
let hash_commitments = hash_fut.try_recv().map_err(ProverError::commit)?;
|
||||
for (commitment, secret) in hash_commitments.into_iter().zip(hash_secrets) {
|
||||
output
|
||||
.transcript_commitments
|
||||
.push(TranscriptCommitment::Hash(commitment));
|
||||
output
|
||||
.transcript_secrets
|
||||
.push(TranscriptSecret::Hash(secret));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
@@ -432,6 +456,23 @@ impl Prover<state::Committed> {
|
||||
let mut builder = ProveConfig::builder(self.transcript());
|
||||
|
||||
if let Some(config) = config.transcript_commit() {
|
||||
// Temporarily, we reject attestation requests which contain hash commitments to
|
||||
// subsets of the transcript. We do this because we want to preserve the
|
||||
// obliviousness of the reference notary, and hash commitments currently leak
|
||||
// the ranges which are being committed.
|
||||
for ((direction, idx), _) in config.iter_hash() {
|
||||
let len = match direction {
|
||||
Direction::Sent => self.transcript().sent().len(),
|
||||
Direction::Received => self.transcript().received().len(),
|
||||
};
|
||||
|
||||
if idx.start() > 0 || idx.end() < len || idx.count() != 1 {
|
||||
return Err(ProverError::attestation(
|
||||
"hash commitments to subsets of the transcript are currently not supported in attestation requests",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
builder.transcript_commit(config.clone());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use tls_core::{anchors::RootCertStore, verify::WebPkiVerifier};
|
||||
use tlsn_common::config::{ProtocolConfig, ProtocolConfigValidator};
|
||||
use tlsn_core::{transcript::Idx, CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig};
|
||||
use tlsn_core::{
|
||||
hash::HashAlgId,
|
||||
transcript::{Idx, TranscriptCommitConfig, TranscriptCommitment, TranscriptCommitmentKind},
|
||||
CryptoProvider, ProveConfig, VerifierOutput, VerifyConfig,
|
||||
};
|
||||
use tlsn_prover::{Prover, ProverConfig};
|
||||
use tlsn_server_fixture::bind;
|
||||
use tlsn_server_fixture_certs::{CA_CERT_DER, SERVER_DOMAIN};
|
||||
@@ -31,7 +35,7 @@ async fn verify() {
|
||||
VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
..
|
||||
transcript_commitments,
|
||||
},
|
||||
) = tokio::join!(prover(socket_0), verifier(socket_1));
|
||||
|
||||
@@ -47,6 +51,11 @@ async fn verify() {
|
||||
&Idx::new(2..transcript.len_received())
|
||||
);
|
||||
assert_eq!(server_name.as_str(), SERVER_DOMAIN);
|
||||
assert!(transcript_commitments
|
||||
.iter()
|
||||
.any(|commitment| matches!(commitment, TranscriptCommitment::Hash { .. })));
|
||||
|
||||
println!("{:?}", transcript_commitments);
|
||||
}
|
||||
|
||||
#[instrument(skip(notary_socket))]
|
||||
@@ -96,7 +105,11 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
tokio::spawn(connection);
|
||||
|
||||
let request = Request::builder()
|
||||
.uri(format!("https://{}", SERVER_DOMAIN))
|
||||
.uri(format!(
|
||||
"https://{}/bytes?size={recv}",
|
||||
SERVER_DOMAIN,
|
||||
recv = MAX_RECV_DATA - 256
|
||||
))
|
||||
.header("Host", SERVER_DOMAIN)
|
||||
.header("Connection", "close")
|
||||
.method("GET")
|
||||
@@ -107,8 +120,7 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
|
||||
assert!(response.status() == StatusCode::OK);
|
||||
|
||||
let payload = response.into_body().collect().await.unwrap().to_bytes();
|
||||
println!("{:?}", &String::from_utf8_lossy(&payload));
|
||||
let _ = response.into_body().collect().await.unwrap().to_bytes();
|
||||
|
||||
let _ = server_task.await.unwrap();
|
||||
|
||||
@@ -116,6 +128,17 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
|
||||
let (sent_len, recv_len) = prover.transcript().len();
|
||||
|
||||
let mut builder = TranscriptCommitConfig::builder(prover.transcript());
|
||||
|
||||
builder.default_kind(TranscriptCommitmentKind::Hash {
|
||||
alg: HashAlgId::SHA256,
|
||||
});
|
||||
|
||||
builder.commit_sent(&(0..sent_len)).unwrap();
|
||||
builder.commit_recv(&(0..recv_len)).unwrap();
|
||||
|
||||
let transcript_commit = builder.build().unwrap();
|
||||
|
||||
let mut builder = ProveConfig::builder(prover.transcript());
|
||||
|
||||
builder
|
||||
@@ -123,7 +146,8 @@ async fn prover<T: AsyncWrite + AsyncRead + Send + Unpin + 'static>(notary_socke
|
||||
.reveal_sent(&(0..sent_len - 1))
|
||||
.unwrap()
|
||||
.reveal_recv(&(2..recv_len))
|
||||
.unwrap();
|
||||
.unwrap()
|
||||
.transcript_commit(transcript_commit);
|
||||
|
||||
let config = builder.build().unwrap();
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ use mpz_vm_core::prelude::*;
|
||||
use serio::{stream::IoStreamExt, SinkExt};
|
||||
use tls_core::msgs::enums::ContentType;
|
||||
use tlsn_common::{
|
||||
commit::commit_records,
|
||||
commit::{commit_records, hash::verify_hash},
|
||||
config::ProtocolConfig,
|
||||
context::build_mt_context,
|
||||
encoding,
|
||||
@@ -382,6 +382,7 @@ impl Verifier<state::Committed> {
|
||||
}
|
||||
|
||||
let mut transcript_commitments = Vec::new();
|
||||
let mut hash_commitments = None;
|
||||
if let Some(commit_config) = transcript_commit {
|
||||
if commit_config.encoding() {
|
||||
let commitment = mux_fut
|
||||
@@ -396,7 +397,12 @@ impl Verifier<state::Committed> {
|
||||
transcript_commitments.push(TranscriptCommitment::Encoding(commitment));
|
||||
}
|
||||
|
||||
// TODO: Other commitment types.
|
||||
if commit_config.has_hash() {
|
||||
hash_commitments = Some(
|
||||
verify_hash(vm, transcript_refs, commit_config.iter_hash().cloned())
|
||||
.map_err(VerifierError::verify)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mux_fut
|
||||
@@ -409,6 +415,12 @@ impl Verifier<state::Committed> {
|
||||
.map_err(VerifierError::verify)?;
|
||||
}
|
||||
|
||||
if let Some(hash_commitments) = hash_commitments {
|
||||
for commitment in hash_commitments.try_recv().map_err(VerifierError::verify)? {
|
||||
transcript_commitments.push(TranscriptCommitment::Hash(commitment));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(VerifierOutput {
|
||||
server_name,
|
||||
transcript,
|
||||
|
||||
Reference in New Issue
Block a user