Skip to content

Commit

Permalink
avoid unnecessary hashing in the Fiat-Shamir heuristic
Browse files Browse the repository at this point in the history
In particular, anything that has already been committed to does not need
to be committed to again. Similarly, if no more randomness is sampled,
changing the Sponge's state is unnecessary.
  • Loading branch information
jan-ferdinand committed Apr 26, 2023
1 parent 4547b96 commit 0bc5f63
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 84 deletions.
18 changes: 9 additions & 9 deletions triton-vm/src/fri.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl<H: AlgebraicHasher> Fri<H> {
.zip_eq(indices.iter())
.map(|(ap, i)| (ap, codeword[*i]))
.collect_vec();
proof_stream.enqueue(&ProofItem::FriResponse(FriResponse(value_ap_pairs)))
proof_stream.enqueue(&ProofItem::FriResponse(FriResponse(value_ap_pairs)), false)
}

/// Given a set of `indices`, a merkle `root`, and the (correctly set) `proof_stream`, verify
Expand All @@ -100,7 +100,7 @@ impl<H: AlgebraicHasher> Fri<H> {
root: Digest,
proof_stream: &mut ProofStream<ProofItem, H>,
) -> Result<Vec<XFieldElement>> {
let fri_response = proof_stream.dequeue()?.as_fri_response()?;
let fri_response = proof_stream.dequeue(false)?.as_fri_response()?;
let FriResponse(dequeued_paths_and_leafs) = fri_response;
let (paths, leaf_values): (Vec<_>, Vec<_>) = dequeued_paths_and_leafs.into_iter().unzip();
let leaf_digests: Vec<_> = leaf_values.par_iter().map(|&xfe| xfe.into()).collect();
Expand Down Expand Up @@ -185,7 +185,7 @@ impl<H: AlgebraicHasher> Fri<H> {
.collect_into_vec(&mut digests);

let mt = Maker::from_digests(&digests);
proof_stream.enqueue(&ProofItem::MerkleRoot(mt.get_root()));
proof_stream.enqueue(&ProofItem::MerkleRoot(mt.get_root()), true);
codewords_and_merkle_trees.push((codeword.clone(), mt));

for _round in 0..num_rounds {
Expand Down Expand Up @@ -218,7 +218,7 @@ impl<H: AlgebraicHasher> Fri<H> {
.collect_into_vec(&mut digests);

let mt = Maker::from_digests(&digests);
proof_stream.enqueue(&ProofItem::MerkleRoot(mt.get_root()));
proof_stream.enqueue(&ProofItem::MerkleRoot(mt.get_root()), true);
codewords_and_merkle_trees.push((codeword.clone(), mt));

// Update subgroup generator and offset
Expand All @@ -227,7 +227,7 @@ impl<H: AlgebraicHasher> Fri<H> {
}

// Send the last codeword in the clear
proof_stream.enqueue(&ProofItem::FriCodeword(codeword));
proof_stream.enqueue(&ProofItem::FriCodeword(codeword), false);

codewords_and_merkle_trees
}
Expand All @@ -247,7 +247,7 @@ impl<H: AlgebraicHasher> Fri<H> {
let mut roots = Vec::with_capacity(num_rounds);
let mut alphas = Vec::with_capacity(num_rounds);

let first_root = proof_stream.dequeue()?.as_merkle_root()?;
let first_root = proof_stream.dequeue(true)?.as_merkle_root()?;
roots.push(first_root);
prof_stop!(maybe_profiler, "init");

Expand All @@ -257,14 +257,14 @@ impl<H: AlgebraicHasher> Fri<H> {
let alpha = proof_stream.sample_scalars(1)[0];
alphas.push(alpha);

let root = proof_stream.dequeue()?.as_merkle_root()?;
let root = proof_stream.dequeue(true)?.as_merkle_root()?;
roots.push(root);
}
prof_stop!(maybe_profiler, "roots and alpha");

prof_start!(maybe_profiler, "last codeword matches root");
// Extract last codeword
let last_codeword = proof_stream.dequeue()?.as_fri_codeword()?;
let last_codeword = proof_stream.dequeue(false)?.as_fri_codeword()?;

// Check if last codeword matches the given root
let codeword_digests = last_codeword.iter().map(|&xfe| xfe.into()).collect_vec();
Expand Down Expand Up @@ -308,7 +308,7 @@ impl<H: AlgebraicHasher> Fri<H> {
proof_stream.sample_indices(self.domain.length, self.colinearity_checks_count);
prof_stop!(maybe_profiler, "sample indices");

prof_start!(maybe_profiler, "dequeue and authenticate");
prof_start!(maybe_profiler, "dequeue and authenticate", "hash");
let mut a_values = Self::dequeue_and_authenticate(&a_indices, roots[0], proof_stream)?;
prof_stop!(maybe_profiler, "dequeue and authenticate");

Expand Down
21 changes: 11 additions & 10 deletions triton-vm/src/proof_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,15 +439,16 @@ mod proof_item_typed_tests {

let mut fs = vec![];
fs.push(proof_stream.sponge_state.state);
proof_stream.enqueue(&ProofItem::AuthenticationPath(map.clone()));
proof_stream.enqueue(&ProofItem::AuthenticationPath(map.clone()), false);
fs.push(proof_stream.sponge_state.state);
proof_stream.enqueue(&ProofItem::CompressedAuthenticationPaths(
auth_struct.clone(),
));
proof_stream.enqueue(
&ProofItem::CompressedAuthenticationPaths(auth_struct.clone()),
false,
);
fs.push(proof_stream.sponge_state.state);
proof_stream.enqueue(&ProofItem::MerkleRoot(root));
proof_stream.enqueue(&ProofItem::MerkleRoot(root), true);
fs.push(proof_stream.sponge_state.state);
proof_stream.enqueue(&ProofItem::FriResponse(fri_response.clone()));
proof_stream.enqueue(&ProofItem::FriResponse(fri_response.clone()), false);
fs.push(proof_stream.sponge_state.state);

let proof = proof_stream.to_proof();
Expand All @@ -459,31 +460,31 @@ mod proof_item_typed_tests {
fs_.push(proof_stream_.sponge_state.state);

let map_ = proof_stream_
.dequeue()
.dequeue(false)
.expect("can't dequeue item")
.as_authentication_path()
.expect("cannot parse dequeued item");
assert_eq!(map, map_);
fs_.push(proof_stream_.sponge_state.state);

let auth_struct_ = proof_stream_
.dequeue()
.dequeue(false)
.expect("can't dequeue item")
.as_compressed_authentication_paths()
.expect("cannot parse dequeued item");
assert_eq!(auth_struct, auth_struct_);
fs_.push(proof_stream_.sponge_state.state);

let root_ = proof_stream_
.dequeue()
.dequeue(true)
.expect("can't dequeue item")
.as_merkle_root()
.expect("cannot parse dequeued item");
assert_eq!(root, root_);
fs_.push(proof_stream_.sponge_state.state);

let fri_response_ = proof_stream_
.dequeue()
.dequeue(false)
.expect("can't dequeue item")
.as_fri_response()
.expect("cannot parse dequeued item");
Expand Down
74 changes: 53 additions & 21 deletions triton-vm/src/proof_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,34 +110,50 @@ where

fn encode_and_pad_item(item: &Item) -> Vec<BFieldElement> {
let encoding = item.encode();
let encoding_append_one = [encoding, vec![BFIELD_ONE]].concat();
let last_chunk_len = encoding_append_one.len() % H::RATE;
let last_chunk_len = (encoding.len() + 1) % H::RATE;
let num_padding_zeros = match last_chunk_len {
0 => 0,
_ => H::RATE - last_chunk_len,
};
[encoding_append_one, vec![BFIELD_ZERO; num_padding_zeros]].concat()
[
encoding,
vec![BFIELD_ONE],
vec![BFIELD_ZERO; num_padding_zeros],
]
.concat()
}

/// Send a proof item as prover to verifier.
pub fn enqueue(&mut self, item: &Item) {
H::absorb_repeatedly(
&mut self.sponge_state,
Self::encode_and_pad_item(item).iter(),
);
/// Some items do not need to be included in the Fiat-Shamir heuristic, _i.e._, they do not
/// need to modify the sponge state. For those items, `include_in_fs_heuristic` should be set
/// to `false`. For example:
/// - Merkle authentication paths do not need to be included (hashed) if the root of the tree
/// in question was included (hashed) previously.
/// - If the proof stream is not used to sample any more randomness, _i.e._, after the last
/// round of interaction, no further items need to be included.
pub fn enqueue(&mut self, item: &Item, include_in_fs_heuristic: bool) {
if include_in_fs_heuristic {
H::absorb_repeatedly(
&mut self.sponge_state,
Self::encode_and_pad_item(item).iter(),
)
}
self.items.push(item.clone());
}

/// Receive a proof item from prover as verifier.
pub fn dequeue(&mut self) -> Result<Item> {
/// See [`ProofStream::enqueue`] for more details.
pub fn dequeue(&mut self, include_in_fs_heuristic: bool) -> Result<Item> {
let item = self
.items
.get(self.items_index)
.ok_or_else(|| ProofStreamError::new("Could not dequeue, queue empty"))?;
H::absorb_repeatedly(
&mut self.sponge_state,
Self::encode_and_pad_item(item).iter(),
);
if include_in_fs_heuristic {
H::absorb_repeatedly(
&mut self.sponge_state,
Self::encode_and_pad_item(item).iter(),
)
}
self.items_index += 1;
Ok(item.clone())
}
Expand Down Expand Up @@ -286,11 +302,11 @@ mod proof_stream_typed_tests {
let manyb2: Vec<BFieldElement> = random_elements(11);

let fs1 = proof_stream.sponge_state.state;
proof_stream.enqueue(&TestItem::ManyB(manyb1.clone()));
proof_stream.enqueue(&TestItem::ManyB(manyb1.clone()), false);
let fs2 = proof_stream.sponge_state.state;
proof_stream.enqueue(&TestItem::ManyX(manyx.clone()));
proof_stream.enqueue(&TestItem::ManyX(manyx.clone()), true);
let fs3 = proof_stream.sponge_state.state;
proof_stream.enqueue(&TestItem::ManyB(manyb2.clone()));
proof_stream.enqueue(&TestItem::ManyB(manyb2.clone()), true);
let fs4 = proof_stream.sponge_state.state;

let proof = proof_stream.to_proof();
Expand All @@ -299,19 +315,31 @@ mod proof_stream_typed_tests {
ProofStream::from_proof(&proof).expect("invalid parsing of proof");

let fs1_ = proof_stream.sponge_state.state;
match proof_stream.dequeue().expect("can't dequeue item").as_bs() {
match proof_stream
.dequeue(false)
.expect("can't dequeue item")
.as_bs()
{
TestItem::ManyB(manyb1_) => assert_eq!(manyb1, manyb1_),
TestItem::ManyX(_) => panic!(),
TestItem::Uncast(_) => panic!(),
};
let fs2_ = proof_stream.sponge_state.state;
match proof_stream.dequeue().expect("can't dequeue item").as_xs() {
match proof_stream
.dequeue(true)
.expect("can't dequeue item")
.as_xs()
{
TestItem::ManyB(_) => panic!(),
TestItem::ManyX(manyx_) => assert_eq!(manyx, manyx_),
TestItem::Uncast(_) => panic!(),
};
let fs3_ = proof_stream.sponge_state.state;
match proof_stream.dequeue().expect("can't dequeue item").as_bs() {
match proof_stream
.dequeue(true)
.expect("can't dequeue item")
.as_bs()
{
TestItem::ManyB(manyb2_) => assert_eq!(manyb2, manyb2_),
TestItem::ManyX(_) => panic!(),
TestItem::Uncast(_) => panic!(),
Expand Down Expand Up @@ -341,11 +369,15 @@ mod proof_stream_typed_tests {
let fri_response = FriResponse(fri_response_content);

let mut proof_stream = ProofStream::<ProofItem, H>::new();
proof_stream.enqueue(&ProofItem::FriResponse(fri_response));
proof_stream.enqueue(&ProofItem::FriResponse(fri_response), true);

// TODO: Also check that deserializing from Proof works here.

let maybe_same_fri_response = proof_stream.dequeue().unwrap().as_fri_response().unwrap();
let maybe_same_fri_response = proof_stream
.dequeue(true)
.unwrap()
.as_fri_response()
.unwrap();
let FriResponse(dequeued_paths_and_leafs) = maybe_same_fri_response;
let (paths, leaf_values): (Vec<_>, Vec<_>) = dequeued_paths_and_leafs.into_iter().unzip();
let maybe_same_leaf_digests = leaf_values.iter().map(H::hash).collect_vec();
Expand Down
Loading

0 comments on commit 0bc5f63

Please sign in to comment.