contract_types/
lib.rs

1#![no_std]
2
3use soroban_sdk::{
4    Bytes, BytesN, Vec, contracterror, contracttype,
5    crypto::bn254::{Bn254G1Affine, Bn254G2Affine},
6};
7
8/// Errors that can occur during Groth16 proof verification.
9#[contracterror]
10#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
11#[repr(u32)]
12pub enum Groth16Error {
13    /// The pairing product did not equal identity.
14    InvalidProof = 0,
15    /// The public inputs length does not match the verification key.
16    MalformedPublicInputs = 1,
17    /// The proof bytes are malformed.
18    MalformedProof = 2,
19    /// The contract was not initialized
20    NotInitialized = 3,
21}
22
23/// Groth16 verification key for BN254 curve (byte-oriented).
24/// All G2 points use Soroban's c1||c0 (imaginary||real) ordering.
25#[contracttype]
26#[derive(Clone)]
27pub struct VerificationKeyBytes {
28    pub alpha: BytesN<64>,
29    pub beta: BytesN<128>,
30    pub gamma: BytesN<128>,
31    pub delta: BytesN<128>,
32    pub ic: Vec<BytesN<64>>,
33}
34
35/// Groth16 proof composed of points A, B, and C.
36/// G2 point B uses Soroban's c1||c0 (imaginary||real) ordering.
37#[derive(Clone)]
38#[contracttype]
39pub struct Groth16Proof {
40    pub a: Bn254G1Affine,
41    pub b: Bn254G2Affine,
42    pub c: Bn254G1Affine,
43}
44
45impl Groth16Proof {
46    /// Returns true if any of the embedded points is empty.
47    pub fn is_empty(&self) -> bool {
48        self.a.to_bytes().is_empty() || self.b.to_bytes().is_empty() || self.c.to_bytes().is_empty()
49    }
50}
51
52/// Size of a single BN254 field element in bytes.
53pub const FIELD_ELEMENT_SIZE: u32 = 32;
54
55/// Size of a G1 point
56pub const G1_SIZE: u32 = FIELD_ELEMENT_SIZE * 2;
57
58/// Size of a G2 point
59pub const G2_SIZE: u32 = FIELD_ELEMENT_SIZE * 4;
60
61/// Total proof size: A (G1) || B (G2) || C (G1) = 64 + 128 + 64 = 256 bytes.
62pub const PROOF_SIZE: u32 = G1_SIZE + G2_SIZE + G1_SIZE;
63
64impl TryFrom<Bytes> for Groth16Proof {
65    type Error = Groth16Error;
66
67    fn try_from(value: Bytes) -> Result<Self, Self::Error> {
68        if value.len() != PROOF_SIZE {
69            return Err(Groth16Error::MalformedProof);
70        }
71
72        let a = Bn254G1Affine::from_bytes(
73            value
74                .slice(0..G1_SIZE)
75                .try_into()
76                .map_err(|_| Groth16Error::MalformedProof)?,
77        );
78        let b = Bn254G2Affine::from_bytes(
79            value
80                .slice(G1_SIZE..G1_SIZE + G2_SIZE)
81                .try_into()
82                .map_err(|_| Groth16Error::MalformedProof)?,
83        );
84        let c = Bn254G1Affine::from_bytes(
85            value
86                .slice(G1_SIZE + G2_SIZE..)
87                .try_into()
88                .map_err(|_| Groth16Error::MalformedProof)?,
89        );
90
91        Ok(Self { a, b, c })
92    }
93}