risc0_ethereum_contracts/
groth16.rs

1// Copyright 2024 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use alloy_sol_types::SolValue;
16use anyhow::Result;
17use risc0_zkvm::{sha::Digestible, Groth16ReceiptVerifierParameters};
18
19#[cfg(feature = "unstable")]
20use alloy_primitives::Bytes;
21#[cfg(feature = "unstable")]
22use risc0_zkvm::{sha::Digest, Groth16Receipt, MaybePruned, Receipt, ReceiptClaim};
23
24#[cfg(feature = "unstable")]
25alloy_sol_types::sol!(
26    #![sol(all_derives)]
27    struct Seal {
28        uint256[2] a;
29        uint256[2][2] b;
30        uint256[2] c;
31    }
32);
33
34#[cfg(feature = "unstable")]
35impl Seal {
36    fn flatten(self) -> Vec<u8> {
37        self.a
38            .iter()
39            .map(|x| x.to_be_bytes_vec())
40            .chain(
41                self.b
42                    .iter()
43                    .flat_map(|x| x.iter().map(|y| y.to_be_bytes_vec())),
44            )
45            .chain(self.c.iter().map(|x| x.to_be_bytes_vec()))
46            .flatten()
47            .collect()
48    }
49
50    /// Convert the [Seal] into a [Receipt] constructed with the given [ReceiptClaim] and
51    /// journal. The verifier parameters are optional and default to the current zkVM version.
52    pub fn to_receipt(
53        self,
54        claim: ReceiptClaim,
55        journal: impl AsRef<[u8]>,
56        verifier_parameters: Option<Digest>,
57    ) -> Receipt {
58        let inner = risc0_zkvm::InnerReceipt::Groth16(Groth16Receipt::new(
59            self.flatten(),
60            MaybePruned::Value(claim),
61            verifier_parameters
62                .unwrap_or_else(|| Groth16ReceiptVerifierParameters::default().digest()),
63        ));
64        Receipt::new(inner, journal.as_ref().to_vec())
65    }
66}
67
68/// Decode a seal with selector as [Bytes] into a [Receipt] constructed with the given
69/// [ReceiptClaim] and journal. The verifier parameters are optional and default to the current zkVM
70/// version.
71#[cfg(feature = "unstable")]
72pub fn decode_groth16_seal(
73    seal: Bytes,
74    claim: ReceiptClaim,
75    journal: impl AsRef<[u8]>,
76    verifier_parameters: Option<Digest>,
77) -> Result<Receipt> {
78    let seal = Seal::abi_decode(&seal[4..], true)?;
79    Ok(seal.to_receipt(claim, journal, verifier_parameters))
80}
81
82/// ABI encoding of the seal.
83pub fn abi_encode(seal: impl AsRef<[u8]>) -> Result<Vec<u8>> {
84    Ok(encode(seal)?.abi_encode())
85}
86
87/// Encoding of a Groth16 seal by prefixing it with the verifier selector.
88///
89/// The verifier selector is determined from the first 4 bytes of the hash of the verifier
90/// parameters including the Groth16 verification key and the control IDs that commit to the RISC
91/// Zero circuits.
92///
93/// NOTE: Selector value of the current zkVM version is used. If you need to use a selector from a
94/// different version of the zkVM, use the [encode_seal](crate::encode_seal) method instead.
95pub fn encode(seal: impl AsRef<[u8]>) -> Result<Vec<u8>> {
96    let verifier_parameters_digest = Groth16ReceiptVerifierParameters::default().digest();
97    let selector = &verifier_parameters_digest.as_bytes()[..4];
98    // Create a new vector with the capacity to hold both selector and seal
99    let mut selector_seal = Vec::with_capacity(selector.len() + seal.as_ref().len());
100    selector_seal.extend_from_slice(selector);
101    selector_seal.extend_from_slice(seal.as_ref());
102
103    Ok(selector_seal)
104}
105
106#[cfg(test)]
107mod tests {
108    use anyhow::anyhow;
109    use regex::Regex;
110
111    use super::*;
112    use std::fs;
113
114    const CONTROL_ID_PATH: &str = "./src/groth16/ControlID.sol";
115    const CONTROL_ROOT: &str = "CONTROL_ROOT";
116    const BN254_CONTROL_ID: &str = "BN254_CONTROL_ID";
117
118    fn parse_digest(file_path: &str, name: &str) -> Result<String, anyhow::Error> {
119        let content = fs::read_to_string(file_path)?;
120        let re_digest = Regex::new(&format!(r#"{}\s*=\s*hex"([0-9a-fA-F]+)""#, name))?;
121        re_digest
122            .captures(&content)
123            .and_then(|caps| caps.get(1).map(|m| m.as_str().to_string()))
124            .ok_or(anyhow!("{name} not found"))
125    }
126    #[test]
127    fn control_root_is_consistent() {
128        let params = Groth16ReceiptVerifierParameters::default();
129        let expected_control_root = params.control_root.to_string();
130        let control_root = parse_digest(CONTROL_ID_PATH, CONTROL_ROOT).unwrap();
131        assert_eq!(control_root, expected_control_root);
132    }
133
134    #[test]
135    fn bn254_control_id_is_consistent() {
136        let params = Groth16ReceiptVerifierParameters::default();
137        let mut expected_bn254_control_id = params.bn254_control_id;
138        expected_bn254_control_id.as_mut_bytes().reverse();
139        let expected_bn254_control_id = hex::encode(expected_bn254_control_id);
140        let bn254_control_id = parse_digest(CONTROL_ID_PATH, BN254_CONTROL_ID).unwrap();
141
142        assert_eq!(bn254_control_id, expected_bn254_control_id);
143    }
144
145    #[test]
146    #[cfg(feature = "unstable")]
147    fn test_decode_seal() {
148        const TEST_RECEIPT_PATH: &str = "./test/TestReceipt.sol";
149        const SEAL: &str = "SEAL";
150        const JOURNAL: &str = "JOURNAL";
151        const IMAGE_ID: &str = "IMAGE_ID";
152        let seal_bytes =
153            Bytes::from(hex::decode(parse_digest(TEST_RECEIPT_PATH, SEAL).unwrap()).unwrap());
154        let journal =
155            Bytes::from(hex::decode(parse_digest(TEST_RECEIPT_PATH, JOURNAL).unwrap()).unwrap())
156                .to_vec();
157        let image_id = Digest::try_from(
158            Bytes::from(hex::decode(parse_digest(TEST_RECEIPT_PATH, IMAGE_ID).unwrap()).unwrap())
159                .as_ref(),
160        )
161        .unwrap();
162        let receipt = decode_groth16_seal(
163            seal_bytes,
164            ReceiptClaim::ok(image_id, journal.clone()),
165            &journal,
166            None,
167        )
168        .unwrap();
169        receipt.verify(image_id).unwrap();
170    }
171
172    #[test]
173    #[cfg(feature = "unstable")]
174    fn test_decode_fake_seal() {
175        use crate::receipt::decode_seal;
176        use risc0_zkvm::ReceiptClaim;
177
178        let fake_claim = ReceiptClaim::ok(Digest::default(), vec![]).digest();
179        let mut seal = vec![];
180        seal.extend_from_slice(&[0xFFu8; 4]);
181        seal.extend_from_slice(fake_claim.as_bytes());
182        decode_seal(seal.into(), fake_claim, vec![]).unwrap();
183    }
184}