risc0_ethereum_contracts/
groth16.rs1use 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 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#[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
82pub fn abi_encode(seal: impl AsRef<[u8]>) -> Result<Vec<u8>> {
84 Ok(encode(seal)?.abi_encode())
85}
86
87pub 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 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}