Skip to main content

hyli_model/
verifiers.rs

1use crate::{
2    Blob, BlobData, BlobIndex, ContractAction, ContractName, Identity, ProgramId, Verifier,
3};
4use alloc::{format, string::String, vec::Vec};
5
6pub const RETH: &str = "reth";
7pub const CAIRO_M: &str = "cairo-m";
8pub const RISC0_3: &str = "risc0-3";
9pub const NOIR: &str = "noir";
10pub const SP1_4: &str = "sp1-4";
11
12#[derive(Debug, Copy, Clone)]
13pub enum NativeVerifiers {
14    Blst,
15    Sha3_256,
16    Secp256k1,
17}
18
19pub const NATIVE_VERIFIERS_CONTRACT_LIST: &[&str] = &["blst", "sha3_256", "secp256k1"];
20
21impl From<NativeVerifiers> for ProgramId {
22    fn from(value: NativeVerifiers) -> Self {
23        match value {
24            NativeVerifiers::Blst => ProgramId("blst".as_bytes().to_vec()),
25            NativeVerifiers::Sha3_256 => ProgramId("sha3_256".as_bytes().to_vec()),
26            NativeVerifiers::Secp256k1 => ProgramId("secp256k1".as_bytes().to_vec()),
27        }
28    }
29}
30
31impl TryFrom<&Verifier> for NativeVerifiers {
32    type Error = String;
33    fn try_from(value: &Verifier) -> Result<Self, Self::Error> {
34        match value.0.as_str() {
35            "blst" => Ok(Self::Blst),
36            "sha3_256" => Ok(Self::Sha3_256),
37            "secp256k1" => Ok(Self::Secp256k1),
38            _ => Err(format!("Unknown native verifier: {value}")),
39        }
40    }
41}
42
43impl From<NativeVerifiers> for Verifier {
44    fn from(value: NativeVerifiers) -> Self {
45        match value {
46            NativeVerifiers::Blst => Self("blst".into()),
47            NativeVerifiers::Sha3_256 => Self("sha3_256".into()),
48            NativeVerifiers::Secp256k1 => Self("secp256k1".into()),
49        }
50    }
51}
52
53#[cfg(feature = "full")]
54mod program_id {
55    use super::*;
56    // Reconstruct the SP1 key structure partially so we can validate without needing the dependency
57    #[derive(serde::Deserialize)]
58    #[allow(unused)]
59    struct SP1VK {
60        vk: SP1StarkVK,
61    }
62    #[derive(serde::Deserialize)]
63    #[allow(unused)]
64    struct SP1StarkVK {
65        commit: serde_json::Value,
66        pc_start: serde_json::Value,
67        initial_global_cumulative_sum: serde_json::Value,
68        chip_information: serde_json::Value,
69        chip_ordering: serde_json::Value,
70    }
71
72    pub fn validate_program_id(
73        verifier: &Verifier,
74        program_id: &ProgramId,
75    ) -> Result<(), anyhow::Error> {
76        match verifier.0.as_str() {
77            RISC0_3 => (!program_id.0.is_empty() && program_id.0.len() <= 32)
78                .then_some(())
79                .ok_or_else(|| {
80                    anyhow::anyhow!("Invalid Risc0 image ID: length must be between 1 and 32 bytes")
81                }),
82            SP1_4 => {
83                serde_json::from_slice::<SP1VK>(program_id.0.as_slice())
84                    .map_err(|e| anyhow::anyhow!("Invalid SP1 image ID: {}", e))?;
85                Ok(())
86            }
87            _ => Ok(()),
88        }
89    }
90}
91
92#[cfg(feature = "full")]
93pub use program_id::validate_program_id;
94
95/// Format of the BlobData for native contract "blst"
96#[derive(Debug, borsh::BorshSerialize, borsh::BorshDeserialize)]
97pub struct BlstSignatureBlob {
98    pub identity: Identity,
99    pub data: Vec<u8>,
100    /// Signature for contatenated data + identity.as_bytes()
101    pub signature: Vec<u8>,
102    pub public_key: Vec<u8>,
103}
104
105impl BlstSignatureBlob {
106    pub fn as_blob(&self) -> Blob {
107        <Self as ContractAction>::as_blob(self, "blst".into(), None, None)
108    }
109}
110
111impl ContractAction for BlstSignatureBlob {
112    fn as_blob(
113        &self,
114        contract_name: ContractName,
115        _caller: Option<BlobIndex>,
116        _callees: Option<Vec<BlobIndex>>,
117    ) -> Blob {
118        #[allow(clippy::expect_used)]
119        Blob {
120            contract_name,
121            data: BlobData(borsh::to_vec(self).expect("failed to encode BlstSignatureBlob")),
122        }
123    }
124}
125
126/// Format of the BlobData for native hash contract like "sha3_256"
127#[derive(Debug, borsh::BorshSerialize, borsh::BorshDeserialize)]
128pub struct ShaBlob {
129    pub identity: Identity,
130    pub data: Vec<u8>,
131    pub sha: Vec<u8>,
132}
133
134impl ShaBlob {
135    pub fn as_blob(&self, contract_name: ContractName) -> Blob {
136        <Self as ContractAction>::as_blob(self, contract_name, None, None)
137    }
138}
139
140impl ContractAction for ShaBlob {
141    fn as_blob(
142        &self,
143        contract_name: ContractName,
144        _caller: Option<BlobIndex>,
145        _callees: Option<Vec<BlobIndex>>,
146    ) -> Blob {
147        #[allow(clippy::expect_used)]
148        Blob {
149            contract_name,
150            data: BlobData(borsh::to_vec(self).expect("failed to encode ShaBlob")),
151        }
152    }
153}
154
155/// Format of the BlobData for native secp256k1 contract
156#[derive(Debug, borsh::BorshSerialize, borsh::BorshDeserialize)]
157pub struct Secp256k1Blob {
158    pub identity: Identity,
159    pub data: [u8; 32],
160    pub public_key: [u8; 33],
161    pub signature: [u8; 64],
162}
163
164impl Secp256k1Blob {
165    #[cfg(all(feature = "full", not(target_arch = "wasm32")))]
166    /// Allow to create a Secp256k1Blob from the data, public_key and signature
167    pub fn new(
168        identity: Identity,
169        data: &[u8],
170        public_key: &str,
171        signature: &str,
172    ) -> anyhow::Result<Self> {
173        use anyhow::Context;
174        use sha2::Digest;
175
176        let public_key = secp256k1::PublicKey::from_slice(
177            &hex::decode(public_key).context("invalid public_key format")?,
178        )
179        .context("cannot parse public_key")?
180        .serialize();
181
182        let signature = secp256k1::ecdsa::Signature::from_der(
183            &hex::decode(signature).context("invalid signature format")?,
184        )
185        .context("cannot parse signature")?
186        .serialize_compact();
187
188        let mut hasher = sha2::Sha256::new();
189        hasher.update(data);
190        let data: [u8; 32] = hasher.finalize().into();
191
192        Ok(Self {
193            identity,
194            data,
195            public_key,
196            signature,
197        })
198    }
199
200    pub fn as_blob(&self) -> Blob {
201        <Self as ContractAction>::as_blob(self, "secp256k1".into(), None, None)
202    }
203}
204
205impl ContractAction for Secp256k1Blob {
206    fn as_blob(
207        &self,
208        contract_name: ContractName,
209        _caller: Option<BlobIndex>,
210        _callees: Option<Vec<BlobIndex>>,
211    ) -> Blob {
212        #[allow(clippy::expect_used)]
213        Blob {
214            contract_name,
215            data: BlobData(borsh::to_vec(self).expect("failed to encode Secp256k1Blob")),
216        }
217    }
218}