-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add simplified
Transcript
trait and implementations (#123)
# Rationale for this change In order to write the verifier in Solidity, we should enable using a Solidity compatibly public-coin transcript. This PR adds an API for doing so. # What changes are included in this PR? * added simplified `Transcript` trait * implemented `Transcript` trait for `merlin::Transcript` * added Solidity compatible `Keccak256Transcript` * added tests for `Transcript` and implementations # Are these changes tested? Yes
- Loading branch information
1 parent
c33f4b7
commit b9ef335
Showing
8 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
55 changes: 55 additions & 0 deletions
55
crates/proof-of-sql/src/base/proof/keccak256_transcript.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
use super::{transcript_core::TranscriptCore, Transcript}; | ||
use core::mem::replace; | ||
use tiny_keccak::{Hasher, Keccak}; | ||
|
||
#[allow(dead_code)] | ||
/// Public coin transcript that is easily portable to Solidity. | ||
/// | ||
/// Leverages the keccak256 hash function, which has the lowest gas costs on Solidity. | ||
/// | ||
/// The public coin transcript consists of alternating prover messages and verifier challenges. | ||
/// In order to multiple verifier challenges in a row, an empty prover message must be sent. | ||
/// In order to send multiple prover messages in a row, the verifier challenge can be discarded. | ||
/// | ||
/// The challenges/state are computed as follows: | ||
/// ```pseudo-code | ||
/// challenge_(i+1) = keccak256(challenge_i, message_(i+1)) | ||
/// ``` | ||
pub struct Keccak256Transcript(Keccak); | ||
impl TranscriptCore for Keccak256Transcript { | ||
fn new() -> Self { | ||
Self(Keccak::v256()) | ||
} | ||
fn raw_append(&mut self, message: &[u8]) { | ||
self.0.update(message) | ||
} | ||
fn raw_challenge(&mut self) -> [u8; 32] { | ||
let mut result = [0; 32]; | ||
|
||
// Replace existing Hasher with a new one, and finalize the old Hasher, | ||
// getting a hash/the desired challenge: | ||
replace(self, Transcript::new()).0.finalize(&mut result); | ||
|
||
// Add this challenge to the new Hasher for the next round of messages: | ||
self.raw_append(&result); | ||
|
||
result | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::{super::transcript_core::test_util::*, Keccak256Transcript}; | ||
#[test] | ||
fn we_get_equivalent_challenges_with_equivalent_keccak256_transcripts() { | ||
we_get_equivalent_challenges_with_equivalent_transcripts::<Keccak256Transcript>() | ||
} | ||
#[test] | ||
fn we_get_different_challenges_with_different_keccak256_transcripts() { | ||
we_get_different_challenges_with_different_transcripts::<Keccak256Transcript>() | ||
} | ||
#[test] | ||
fn we_get_different_nontrivial_consecutive_challenges_from_keccak256_transcript() { | ||
we_get_different_nontrivial_consecutive_challenges_from_transcript::<Keccak256Transcript>() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
use crate::base::scalar::Scalar; | ||
use zerocopy::{AsBytes, FromBytes}; | ||
|
||
/// A public-coin transcript. | ||
/// | ||
/// This trait contains several method for adding prover messages and computing verifier challenges. | ||
/// | ||
/// Implementation note: this is intended to be implemented via [super::transcript_core::TranscriptCore] rather than directly. | ||
#[allow(dead_code)] | ||
pub trait Transcript { | ||
/// Creates a new transcript | ||
fn new() -> Self; | ||
/// Appends the provided messages by appending the reversed raw bytes (i.e. assuming the message is bigendian) | ||
fn extend_as_be<M: FromBytes + AsBytes>(&mut self, messages: impl IntoIterator<Item = M>); | ||
/// Appends the provided messages by appending the raw bytes (i.e. assuming the message is littleendian) | ||
fn extend_as_le_from_refs<'a, M: AsBytes + 'a>( | ||
&mut self, | ||
messages: impl IntoIterator<Item = &'a M>, | ||
); | ||
/// Appends the provided scalars by appending the reversed raw bytes of the canonical value of the scalar (i.e. bigendian form) | ||
fn extend_scalars_as_be<'a, S: Scalar + 'a>( | ||
&mut self, | ||
messages: impl IntoIterator<Item = &'a S>, | ||
); | ||
/// Request a scalar challenge. Assumes that the reversed raw bytes are the canonical value of the scalar (i.e. bigendian form) | ||
fn scalar_challenge_as_be<S: Scalar>(&mut self) -> S; | ||
#[cfg(test)] | ||
/// Request a challenge. Returns the raw, unreversed, bytes. (i.e. littleendian form) | ||
fn challenge_as_le(&mut self) -> [u8; 32]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use super::Transcript; | ||
use crate::base::{ref_into::RefInto, scalar::Scalar}; | ||
use zerocopy::{AsBytes, FromBytes}; | ||
|
||
/// A trait used to facilitate implementation of [Transcript](super::Transcript). | ||
/// | ||
/// There is a blanket `impl<T: TranscriptCore> Transcript for T` implementation. | ||
pub(super) trait TranscriptCore { | ||
/// Creates a new transcript. | ||
fn new() -> Self; | ||
/// Appends a slice of bytes (as a message) to the transcript. | ||
fn raw_append(&mut self, message: &[u8]); | ||
/// Pulls a challenge from the transcript. | ||
fn raw_challenge(&mut self) -> [u8; 32]; | ||
} | ||
|
||
/// private method to facilitate recieving challenges and reversing them. Undefined behavior if the size_of `M` is not 32 bytes. | ||
fn receive_challenge_as_be<M: FromBytes>(slf: &mut impl TranscriptCore) -> M { | ||
debug_assert_eq!(32, core::mem::size_of::<M>()); | ||
let mut bytes = slf.raw_challenge(); | ||
bytes.reverse(); | ||
M::read_from(&bytes).unwrap() | ||
} | ||
|
||
impl<T: TranscriptCore> Transcript for T { | ||
fn new() -> Self { | ||
TranscriptCore::new() | ||
} | ||
fn extend_as_be<M: FromBytes + AsBytes>(&mut self, messages: impl IntoIterator<Item = M>) { | ||
messages.into_iter().for_each(|mut message| { | ||
let bytes = message.as_bytes_mut(); | ||
bytes.reverse(); | ||
self.raw_append(bytes) | ||
}) | ||
} | ||
fn extend_as_le_from_refs<'a, M: AsBytes + 'a>( | ||
&mut self, | ||
messages: impl IntoIterator<Item = &'a M>, | ||
) { | ||
messages | ||
.into_iter() | ||
.for_each(|message| self.raw_append(message.as_bytes())) | ||
} | ||
fn extend_scalars_as_be<'a, S: Scalar + 'a>( | ||
&mut self, | ||
messages: impl IntoIterator<Item = &'a S>, | ||
) { | ||
self.extend_as_be::<[u64; 4]>(messages.into_iter().map(RefInto::ref_into)) | ||
} | ||
fn scalar_challenge_as_be<S: Scalar>(&mut self) -> S { | ||
receive_challenge_as_be::<[u64; 4]>(self).into() | ||
} | ||
#[cfg(test)] | ||
fn challenge_as_le(&mut self) -> [u8; 32] { | ||
self.raw_challenge() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
pub(super) mod test_util { | ||
use super::TranscriptCore; | ||
pub fn we_get_equivalent_challenges_with_equivalent_transcripts<T: TranscriptCore>() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
transcript1.raw_append(b"message"); | ||
|
||
let mut transcript2: T = TranscriptCore::new(); | ||
transcript2.raw_append(b"message"); | ||
|
||
assert_eq!( | ||
transcript1.raw_challenge(), | ||
transcript2.raw_challenge(), | ||
"challenges do not match when transcripts are the same" | ||
); | ||
} | ||
pub fn we_get_different_challenges_with_different_transcripts<T: TranscriptCore>() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
transcript1.raw_append(b"message1"); | ||
|
||
let mut transcript2: T = TranscriptCore::new(); | ||
transcript2.raw_append(b"message2"); | ||
|
||
assert_ne!( | ||
transcript1.raw_challenge(), | ||
transcript2.raw_challenge(), | ||
"challenges match even though transcripts are different" | ||
); | ||
} | ||
pub fn we_get_different_nontrivial_consecutive_challenges_from_transcript<T: TranscriptCore>() { | ||
let mut transcript: T = TranscriptCore::new(); | ||
let challenge1 = transcript.raw_challenge(); | ||
let challenge2 = transcript.raw_challenge(); | ||
|
||
assert_ne!( | ||
challenge1, [0; 32], | ||
"first challenge in transcript is trivial" | ||
); | ||
assert_ne!( | ||
challenge2, [0; 32], | ||
"second challenge in transcript is trivial" | ||
); | ||
assert_ne!( | ||
challenge1, challenge2, | ||
"consequtive challenges match even though transcripts are different" | ||
); | ||
} | ||
} |
72 changes: 72 additions & 0 deletions
72
crates/proof-of-sql/src/base/proof/transcript_core_test.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use super::{transcript_core::TranscriptCore, Keccak256Transcript as T, Transcript}; | ||
use crate::base::scalar::Curve25519Scalar as S; | ||
use zerocopy::AsBytes; | ||
#[test] | ||
fn we_can_add_values_to_the_transcript_in_big_endian_form() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
transcript1.extend_as_be([1u16, 1000, 2]); | ||
|
||
let mut transcript2: T = TranscriptCore::new(); | ||
transcript2.raw_append(&[0, 1]); | ||
transcript2.raw_append(&[3, 232]); | ||
transcript2.raw_append(&[0, 2]); | ||
|
||
assert_eq!(transcript1.raw_challenge(), transcript2.raw_challenge()); | ||
} | ||
|
||
#[test] | ||
fn we_can_add_values_to_the_transcript_in_little_endian_form() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
transcript1.extend_as_le_from_refs(&[1u16, 1000, 2]); | ||
|
||
let mut transcript2: T = TranscriptCore::new(); | ||
transcript2.raw_append(&[1, 0]); | ||
transcript2.raw_append(&[232, 3]); | ||
transcript2.raw_append(&[2, 0]); | ||
|
||
assert_eq!(transcript1.raw_challenge(), transcript2.raw_challenge()); | ||
} | ||
|
||
#[test] | ||
fn we_can_add_scalars_to_the_transcript_in_big_endian_form() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
transcript1.extend_scalars_as_be(&[S::from(1), S::from(1000), S::from(2)]); | ||
|
||
let mut transcript2: T = TranscriptCore::new(); | ||
transcript2.raw_append(&[ | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 1, | ||
]); | ||
transcript2.raw_append(&[ | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
3, 232, | ||
]); | ||
transcript2.raw_append(&[ | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 2, | ||
]); | ||
|
||
assert_eq!(transcript1.raw_challenge(), transcript2.raw_challenge()); | ||
} | ||
|
||
#[test] | ||
fn we_can_get_challenge_as_scalar_interpreted_as_big_endian() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
let scalar: S = transcript1.scalar_challenge_as_be(); | ||
|
||
let mut transcript2: T = TranscriptCore::new(); | ||
let mut bytes = transcript2.raw_challenge(); | ||
bytes.reverse(); | ||
let mut limbs: [u64; 4] = scalar.into(); | ||
limbs.as_bytes_mut().copy_from_slice(&bytes); | ||
|
||
assert_eq!(scalar, limbs.into()); | ||
} | ||
|
||
#[test] | ||
fn we_can_get_challenge_as_little_endian() { | ||
let mut transcript1: T = TranscriptCore::new(); | ||
let mut transcript2: T = TranscriptCore::new(); | ||
|
||
assert_eq!(transcript1.raw_challenge(), transcript2.challenge_as_le()); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters