Skip to content

Commit 1c31aa3

Browse files
authored
Add borsh-serialization to verifier (#60)
* feat: add borsh serialization to sp1 groth16 verifier * feat: add no-op verifier
1 parent dd5d2ad commit 1c31aa3

File tree

7 files changed

+203
-8
lines changed

7 files changed

+203
-8
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

adapters/sp1/groth16-verifier/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ rust.unused_must_use = "deny"
1313
rustdoc.all = "warn"
1414

1515
[dependencies]
16-
serde.workspace = true
17-
hex.workspace = true
1816
blake3 = { version = "1.6.1", default-features = false }
1917
bn = { version = "0.6.0", package = "substrate-bn" }
18+
borsh.workspace = true
19+
hex.workspace = true
20+
serde.workspace = true
2021
sha2.workspace = true
2122
thiserror.workspace = true
2223
zkaleido.workspace = true

adapters/sp1/groth16-verifier/src/types/g1.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::fmt;
22

33
use bn::{AffineG1, Fq, Group, G1};
4+
use borsh::{BorshDeserialize, BorshSerialize};
45
use serde::{Deserialize, Serialize, Serializer};
56

67
use crate::{
@@ -170,3 +171,65 @@ fn get_ys_from_x_g1(x: Fq) -> Result<(Fq, Fq), Error> {
170171
Ok((neg_y, y))
171172
}
172173
}
174+
175+
impl BorshSerialize for SAffineG1 {
176+
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
177+
// Convert to projective to access coordinates
178+
let mut projective: G1 = (self.0).into();
179+
projective.normalize();
180+
let (x, y) = (projective.x(), projective.y());
181+
182+
// Serialize x coordinate
183+
let mut x_bytes = [0u8; 32];
184+
x.to_big_endian(&mut x_bytes).map_err(|_| {
185+
std::io::Error::new(
186+
std::io::ErrorKind::InvalidData,
187+
"Failed to serialize x coordinate",
188+
)
189+
})?;
190+
writer.write_all(&x_bytes)?;
191+
192+
// Serialize y coordinate
193+
let mut y_bytes = [0u8; 32];
194+
y.to_big_endian(&mut y_bytes).map_err(|_| {
195+
std::io::Error::new(
196+
std::io::ErrorKind::InvalidData,
197+
"Failed to serialize y coordinate",
198+
)
199+
})?;
200+
writer.write_all(&y_bytes)?;
201+
202+
Ok(())
203+
}
204+
}
205+
206+
impl BorshDeserialize for SAffineG1 {
207+
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
208+
// Read x coordinate
209+
let mut x_bytes = [0u8; 32];
210+
reader.read_exact(&mut x_bytes)?;
211+
let x = Fq::from_slice(&x_bytes).map_err(|_| {
212+
std::io::Error::new(
213+
std::io::ErrorKind::InvalidData,
214+
"Failed to deserialize x coordinate",
215+
)
216+
})?;
217+
218+
// Read y coordinate
219+
let mut y_bytes = [0u8; 32];
220+
reader.read_exact(&mut y_bytes)?;
221+
let y = Fq::from_slice(&y_bytes).map_err(|_| {
222+
std::io::Error::new(
223+
std::io::ErrorKind::InvalidData,
224+
"Failed to deserialize y coordinate",
225+
)
226+
})?;
227+
228+
let z = Fq::one();
229+
let projective = G1::new(x, y, z);
230+
let g1 = AffineG1::from_jacobian(projective)
231+
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid point"))?;
232+
233+
Ok(SAffineG1(g1))
234+
}
235+
}

adapters/sp1/groth16-verifier/src/types/g2.rs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{cmp::Ordering, fmt};
22

33
use bn::{AffineG2, Fq, Fq2, Group, G2};
4+
use borsh::{BorshDeserialize, BorshSerialize};
45
use serde::{Deserialize, Serialize, Serializer};
56

67
use crate::{
@@ -218,3 +219,107 @@ fn get_ys_from_x_g2(x: Fq2) -> Result<(Fq2, Fq2), Error> {
218219
Ok((neg_y, y))
219220
}
220221
}
222+
223+
impl BorshSerialize for SAffineG2 {
224+
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
225+
let mut projective: G2 = (self.0).into();
226+
projective.normalize();
227+
let (x, y) = (projective.x(), projective.y());
228+
229+
// Serialize x coordinate (Fq2: real + imaginary)
230+
let mut x_real_bytes = [0u8; 32];
231+
x.real().to_big_endian(&mut x_real_bytes).map_err(|_| {
232+
std::io::Error::new(
233+
std::io::ErrorKind::InvalidData,
234+
"Failed to serialize x real part",
235+
)
236+
})?;
237+
writer.write_all(&x_real_bytes)?;
238+
239+
let mut x_imag_bytes = [0u8; 32];
240+
x.imaginary()
241+
.to_big_endian(&mut x_imag_bytes)
242+
.map_err(|_| {
243+
std::io::Error::new(
244+
std::io::ErrorKind::InvalidData,
245+
"Failed to serialize x imaginary part",
246+
)
247+
})?;
248+
writer.write_all(&x_imag_bytes)?;
249+
250+
// Serialize y coordinate (Fq2: real + imaginary)
251+
let mut y_real_bytes = [0u8; 32];
252+
y.real().to_big_endian(&mut y_real_bytes).map_err(|_| {
253+
std::io::Error::new(
254+
std::io::ErrorKind::InvalidData,
255+
"Failed to serialize y real part",
256+
)
257+
})?;
258+
writer.write_all(&y_real_bytes)?;
259+
260+
let mut y_imag_bytes = [0u8; 32];
261+
y.imaginary()
262+
.to_big_endian(&mut y_imag_bytes)
263+
.map_err(|_| {
264+
std::io::Error::new(
265+
std::io::ErrorKind::InvalidData,
266+
"Failed to serialize y imaginary part",
267+
)
268+
})?;
269+
writer.write_all(&y_imag_bytes)?;
270+
271+
Ok(())
272+
}
273+
}
274+
275+
impl BorshDeserialize for SAffineG2 {
276+
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
277+
// Read x coordinate components
278+
let mut x_real_bytes = [0u8; 32];
279+
reader.read_exact(&mut x_real_bytes)?;
280+
let x_real = Fq::from_slice(&x_real_bytes).map_err(|_| {
281+
std::io::Error::new(
282+
std::io::ErrorKind::InvalidData,
283+
"Failed to deserialize x real part",
284+
)
285+
})?;
286+
287+
let mut x_imag_bytes = [0u8; 32];
288+
reader.read_exact(&mut x_imag_bytes)?;
289+
let x_imag = Fq::from_slice(&x_imag_bytes).map_err(|_| {
290+
std::io::Error::new(
291+
std::io::ErrorKind::InvalidData,
292+
"Failed to deserialize x imaginary part",
293+
)
294+
})?;
295+
296+
// Read y coordinate components
297+
let mut y_real_bytes = [0u8; 32];
298+
reader.read_exact(&mut y_real_bytes)?;
299+
let y_real = Fq::from_slice(&y_real_bytes).map_err(|_| {
300+
std::io::Error::new(
301+
std::io::ErrorKind::InvalidData,
302+
"Failed to deserialize y real part",
303+
)
304+
})?;
305+
306+
let mut y_imag_bytes = [0u8; 32];
307+
reader.read_exact(&mut y_imag_bytes)?;
308+
let y_imag = Fq::from_slice(&y_imag_bytes).map_err(|_| {
309+
std::io::Error::new(
310+
std::io::ErrorKind::InvalidData,
311+
"Failed to deserialize y imaginary part",
312+
)
313+
})?;
314+
315+
let x = Fq2::new(x_real, x_imag);
316+
let y = Fq2::new(y_real, y_imag);
317+
let z = Fq2::one();
318+
319+
let projective = G2::new(x, y, z);
320+
let g2 = AffineG2::from_jacobian(projective)
321+
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid point"))?;
322+
323+
Ok(SAffineG2(g2))
324+
}
325+
}

adapters/sp1/groth16-verifier/src/types/proof.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use borsh::{BorshDeserialize, BorshSerialize};
12
use serde::{Deserialize, Serialize};
23

34
use crate::{
@@ -15,7 +16,7 @@ use crate::{
1516
pub(crate) const GROTH16_PROOF_LENGTH: usize = 256;
1617

1718
/// Proof for the Groth16 verification.
18-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
19+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
1920
pub(crate) struct Groth16Proof {
2021
pub(crate) ar: SAffineG1,
2122
pub(crate) krs: SAffineG1,

adapters/sp1/groth16-verifier/src/types/vk.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use bn::{AffineG2, G2};
2+
use borsh::{BorshDeserialize, BorshSerialize};
23
use serde::{Deserialize, Serialize};
34

45
use crate::{
@@ -10,22 +11,22 @@ use crate::{
1011
};
1112

1213
/// G1 elements of the verification key.
13-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
14+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
1415
pub(crate) struct Groth16G1 {
1516
pub(crate) alpha: SAffineG1,
1617
pub(crate) k: Vec<SAffineG1>,
1718
}
1819

1920
/// G2 elements of the verification key.
20-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
2122
pub(crate) struct Groth16G2 {
2223
pub(crate) beta: SAffineG2,
2324
pub(crate) delta: SAffineG2,
2425
pub(crate) gamma: SAffineG2,
2526
}
2627

2728
/// Verification key for the Groth16 proof.
28-
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
29+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
2930
pub(crate) struct Groth16VerifyingKey {
3031
pub(crate) g1: Groth16G1,
3132
pub(crate) g2: Groth16G2,
@@ -108,4 +109,14 @@ mod tests {
108109

109110
assert_eq!(vk, deserialized);
110111
}
112+
113+
#[test]
114+
fn test_vk_borsh() {
115+
let vk = Groth16VerifyingKey::load_from_gnark_bytes(&GROTH16_VK_BYTES).unwrap();
116+
117+
let serialized = borsh::to_vec(&vk).unwrap();
118+
let deserialized: Groth16VerifyingKey = borsh::from_slice(&serialized).unwrap();
119+
120+
assert_eq!(vk, deserialized);
121+
}
111122
}

zkaleido/src/verifier.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::fmt::Debug;
22

3-
use borsh::BorshDeserialize;
4-
use serde::{de::DeserializeOwned, Serialize};
3+
use borsh::{BorshDeserialize, BorshSerialize};
4+
use serde::{de::DeserializeOwned, Deserialize, Serialize};
55

66
use crate::{
77
ProofReceipt, ProofReceiptWithMetadata, PublicValues, VerifyingKey, VerifyingKeyCommitment,
@@ -77,3 +77,16 @@ pub trait ZkVmOutputExtractor: Send + Sync + Clone + Debug + 'static {
7777
.map_err(|e| ZkVmError::OutputExtractionError { source: e.into() })
7878
}
7979
}
80+
81+
/// A no-op verifier: its `verify` method does nothing and always returns success.
82+
///
83+
/// Use this when you want to bypass verification logic entirely. It performs
84+
/// no cryptographic checks on the proof receipt.
85+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
86+
pub struct NoopVerifier;
87+
88+
impl ZkVmVerifier for NoopVerifier {
89+
fn verify(&self, _receipt: &ProofReceipt) -> ZkVmResult<()> {
90+
Ok(())
91+
}
92+
}

0 commit comments

Comments
 (0)