jf_plonk/transcript/
solidity.rs

1// Copyright (c) 2022 Espresso Systems (espressosys.com)
2// This file is part of the Jellyfish library.
3
4// You should have received a copy of the MIT License
5// along with the Jellyfish library. If not, see <https://mit-license.org/>.
6
7//! This module implements solidity transcript.
8use super::PlonkTranscript;
9use crate::{
10    constants::KECCAK256_STATE_SIZE, errors::PlonkError, proof_system::structs::VerifyingKey,
11};
12use ark_ec::{
13    pairing::Pairing,
14    short_weierstrass::{Affine, SWCurveConfig},
15    AffineRepr,
16};
17use ark_ff::{BigInteger, PrimeField};
18use ark_std::vec::Vec;
19use jf_pcs::prelude::Commitment;
20use jf_utils::to_bytes;
21use sha3::{Digest, Keccak256};
22
23/// Transcript with `keccak256` hash function.
24///
25/// We append new elements to the transcript vector,
26/// and when a challenge is generated, the state is updated and transcript is
27/// emptied.
28///
29/// 1. state = hash(state | transcript)
30/// 2. transcript = Vec::new()
31/// 3. challenge = bytes_to_field(state)
32pub struct SolidityTranscript {
33    pub(crate) state: [u8; KECCAK256_STATE_SIZE],
34    pub(crate) transcript: Vec<u8>,
35}
36
37impl<F: PrimeField> PlonkTranscript<F> for SolidityTranscript {
38    /// Create a new plonk transcript. `label` is omitted for efficiency.
39    fn new(_label: &'static [u8]) -> Self {
40        SolidityTranscript {
41            state: [0u8; KECCAK256_STATE_SIZE],
42            transcript: Vec::new(),
43        }
44    }
45    /// Append the message to the transcript. `_label` is omitted for
46    /// efficiency.
47    fn append_message(&mut self, _label: &'static [u8], msg: &[u8]) -> Result<(), PlonkError> {
48        // We remove the labels for better efficiency
49        self.transcript.extend_from_slice(msg);
50        Ok(())
51    }
52
53    // override default implementation since we want to use BigEndian serialization
54    fn append_commitment<E, P>(
55        &mut self,
56        label: &'static [u8],
57        comm: &Commitment<E>,
58    ) -> Result<(), PlonkError>
59    where
60        E: Pairing<BaseField = F, G1Affine = Affine<P>>,
61        P: SWCurveConfig<BaseField = F>,
62    {
63        let zero = F::zero();
64        let (x, y) = if comm.0.is_zero() {
65            // this is solidity precompile representation of Points of Infinity
66            (&zero, &zero)
67        } else {
68            comm.0.xy().unwrap()
69        };
70
71        <Self as PlonkTranscript<F>>::append_message(
72            self,
73            label,
74            &[x.into_bigint().to_bytes_be(), y.into_bigint().to_bytes_be()].concat(),
75        )
76    }
77
78    // override default implementation since we want to use BigEndian serialization
79    fn append_field_elem<E>(
80        &mut self,
81        label: &'static [u8],
82        challenge: &E::ScalarField,
83    ) -> Result<(), PlonkError>
84    where
85        E: Pairing<BaseField = F>,
86    {
87        <Self as PlonkTranscript<F>>::append_message(
88            self,
89            label,
90            &challenge.into_bigint().to_bytes_be(),
91        )
92    }
93
94    fn append_vk_and_pub_input<E, P>(
95        &mut self,
96        vk: &VerifyingKey<E>,
97        pub_input: &[E::ScalarField],
98    ) -> Result<(), PlonkError>
99    where
100        E: Pairing<BaseField = F, G1Affine = Affine<P>>,
101        E::ScalarField: PrimeField,
102        P: SWCurveConfig<BaseField = F>,
103    {
104        <Self as PlonkTranscript<F>>::append_message(
105            self,
106            b"field size in bits",
107            E::ScalarField::MODULUS_BIT_SIZE.to_be_bytes().as_ref(),
108        )?;
109        <Self as PlonkTranscript<F>>::append_message(
110            self,
111            b"domain size",
112            vk.domain_size.to_be_bytes().as_ref(),
113        )?;
114        <Self as PlonkTranscript<F>>::append_message(
115            self,
116            b"input size",
117            vk.num_inputs.to_be_bytes().as_ref(),
118        )?;
119        // in EVM, memory word size is 32 bytes, the first 3 fields put onto the
120        // transcript occupies 4+8+8=20 bytes, thus to align with the memory
121        // boundray, we pad with 12 bytes of zeros.
122        <Self as PlonkTranscript<F>>::append_message(
123            self,
124            b"EVM word alignment padding",
125            &[0u8; 12],
126        )?;
127
128        // include [x]_2 G2 point from SRS
129        // all G1 points from SRS are implicit reflected in committed polys
130        //
131        // Since this is a fixed value, we don't need solidity-efficient serialization,
132        // we simply append the `to_bytes!()` which uses compressed, little-endian form
133        // instead of other proof-dependent field like number of public inputs or
134        // concrete polynomial commitments which uses uncompressed, big-endian
135        // form.
136        <Self as PlonkTranscript<F>>::append_message(
137            self,
138            b"SRS G2 element",
139            &to_bytes!(&vk.open_key.powers_of_h[1])?,
140        )?;
141
142        self.append_field_elems::<E>(b"wire subsets separators", &vk.k)?;
143        self.append_commitments(b"selector commitments", &vk.selector_comms)?;
144        self.append_commitments(b"sigma commitments", &vk.sigma_comms)?;
145        self.append_field_elems::<E>(b"public input", pub_input)
146    }
147
148    fn get_challenge<E>(&mut self, _label: &'static [u8]) -> Result<E::ScalarField, PlonkError>
149    where
150        E: Pairing<BaseField = F>,
151        E::ScalarField: PrimeField,
152    {
153        // 1. state = hash(state | transcript)
154        let mut hasher = Keccak256::new();
155        hasher.update(self.state);
156        hasher.update(&self.transcript);
157        let buf = hasher.finalize();
158        self.state.copy_from_slice(&buf);
159
160        // 2. transcript = Vec::new()
161        self.transcript = Vec::new();
162
163        // 3. challenge = bytes_to_field(state)
164        Ok(E::ScalarField::from_be_bytes_mod_order(&buf))
165    }
166}
167
168#[test]
169fn test_solidity_keccak() {
170    use hex::FromHex;
171    use sha3::{Digest, Keccak256};
172    let message = "the quick brown fox jumps over the lazy dog".as_bytes();
173
174    let mut hasher = Keccak256::new();
175    hasher.update(message);
176    let output = hasher.finalize();
177
178    // test example result yanked from smart contract execution
179    assert_eq!(
180        output[..],
181        <[u8; 32]>::from_hex("865bf05cca7ba26fb8051e8366c6d19e21cadeebe3ee6bfa462b5c72275414ec")
182            .unwrap()
183    );
184}