aztec_pxe/kernel/
oracle.rs

1//! Private kernel oracle for providing hints during kernel circuit execution.
2//!
3//! Ports the TS `PrivateKernelOracle` which provides contract preimages,
4//! membership witnesses, VK witnesses, and other data needed by kernel circuits.
5
6use aztec_core::abi::{ContractArtifact, FunctionSelector};
7use aztec_core::error::Error;
8use aztec_core::hash::{
9    compute_artifact_hash, compute_private_functions_root_from_artifact,
10    compute_public_bytecode_commitment, compute_salted_initialization_hash,
11};
12use aztec_core::types::{AztecAddress, Fr};
13use aztec_node_client::AztecNode;
14
15use crate::stores::{ContractStore, KeyStore};
16
17/// Oracle for private kernel circuit interactions with state trees.
18///
19/// Provides hints and data lookups needed during kernel circuit execution:
20/// - Contract address and class preimages
21/// - Function membership witnesses
22/// - VK membership witnesses in the protocol VK tree
23/// - Note hash and nullifier tree membership witnesses
24/// - Master secret keys for key verification
25pub struct PrivateKernelOracle<'a, N: AztecNode> {
26    node: &'a N,
27    contract_store: &'a ContractStore,
28    key_store: &'a KeyStore,
29    /// Block hash for consistent state reads.
30    block_hash: Fr,
31}
32
33impl<'a, N: AztecNode> PrivateKernelOracle<'a, N> {
34    pub fn new(
35        node: &'a N,
36        contract_store: &'a ContractStore,
37        key_store: &'a KeyStore,
38        block_hash: Fr,
39    ) -> Self {
40        Self {
41            node,
42            contract_store,
43            key_store,
44            block_hash,
45        }
46    }
47
48    /// Get the contract address preimage (instance data including salted init hash).
49    ///
50    /// Returns the contract instance data needed to verify the contract address
51    /// derivation inside the kernel circuit.
52    pub async fn get_contract_address_preimage(
53        &self,
54        address: &AztecAddress,
55    ) -> Result<serde_json::Value, Error> {
56        let instance = if let Some(instance) = self.contract_store.get_instance(address).await? {
57            instance
58        } else {
59            match self.node.get_contract(address).await? {
60                Some(instance) => instance,
61                None => {
62                    return Err(Error::InvalidData(format!(
63                        "contract instance not found for address {address}"
64                    )))
65                }
66            }
67        };
68
69        let salted_initialization_hash = compute_salted_initialization_hash(
70            instance.inner.salt,
71            instance.inner.initialization_hash,
72            instance.inner.deployer,
73        );
74
75        Ok(serde_json::json!({
76            "address": instance.address,
77            "saltedInitializationHash": salted_initialization_hash,
78            "version": instance.inner.version,
79            "salt": instance.inner.salt,
80            "deployer": instance.inner.deployer,
81            "currentContractClassId": instance.inner.current_contract_class_id,
82            "originalContractClassId": instance.inner.original_contract_class_id,
83            "initializationHash": instance.inner.initialization_hash,
84            "publicKeys": instance.inner.public_keys,
85        }))
86    }
87
88    fn contract_class_preimage(artifact: &ContractArtifact) -> Result<serde_json::Value, Error> {
89        let artifact_hash = compute_artifact_hash(artifact);
90        let private_functions_root = compute_private_functions_root_from_artifact(artifact)?;
91        let public_bytecode_commitment =
92            compute_public_bytecode_commitment(&extract_packed_public_bytecode(artifact));
93
94        Ok(serde_json::json!({
95            "artifactHash": artifact_hash,
96            "privateFunctionsRoot": private_functions_root,
97            "publicBytecodeCommitment": public_bytecode_commitment,
98        }))
99    }
100
101    /// Get the contract class ID preimage (artifact hash, bytecode commitment).
102    ///
103    /// Returns the contract class data needed to verify class ID derivation.
104    pub async fn get_contract_class_id_preimage(
105        &self,
106        class_id: &Fr,
107    ) -> Result<serde_json::Value, Error> {
108        if let Some(artifact) = self.contract_store.get_artifact(class_id).await? {
109            return Self::contract_class_preimage(&artifact);
110        }
111
112        match self.node.get_contract_class(class_id).await? {
113            Some(class_data) => Ok(class_data),
114            None => Err(Error::InvalidData(format!(
115                "contract class not found for id {class_id}"
116            ))),
117        }
118    }
119
120    /// Get function membership witness in the contract's private function tree.
121    ///
122    /// Proves that a function selector belongs to a specific contract class.
123    pub async fn get_function_membership_witness(
124        &self,
125        class_id: &Fr,
126        function_selector: &Fr,
127    ) -> Result<serde_json::Value, Error> {
128        Err(Error::InvalidData(format!(
129            "private function membership witness for class {class_id} selector {function_selector} is not implemented yet"
130        )))
131    }
132
133    /// Get VK membership witness in the protocol VK indexed merkle tree.
134    ///
135    /// Proves that a verification key is part of the protocol's VK tree.
136    pub async fn get_vk_membership_witness(
137        &self,
138        vk_hash: &Fr,
139    ) -> Result<serde_json::Value, Error> {
140        Err(Error::InvalidData(format!(
141            "protocol VK membership witness for vk hash {vk_hash} is not implemented yet"
142        )))
143    }
144
145    /// Get note hash membership witness at the current block.
146    pub async fn get_note_hash_membership_witness(
147        &self,
148        note_hash: &Fr,
149    ) -> Result<Option<serde_json::Value>, Error> {
150        // Use block number 0 to indicate "at the anchor block"
151        self.node
152            .get_note_hash_membership_witness(0, note_hash)
153            .await
154    }
155
156    /// Get nullifier membership witness at the current block.
157    pub async fn get_nullifier_membership_witness(
158        &self,
159        nullifier: &Fr,
160    ) -> Result<Option<serde_json::Value>, Error> {
161        self.node
162            .get_nullifier_membership_witness(0, nullifier)
163            .await
164    }
165
166    /// Get the note hash tree root from the block header.
167    pub async fn get_note_hash_tree_root(&self) -> Result<Fr, Error> {
168        let header = self.node.get_block_header(0).await?;
169        // Extract note hash tree root from header JSON
170        if let Some(root) = header
171            .pointer("/state/partial/noteHashTree/root")
172            .and_then(|v| v.as_str())
173        {
174            Fr::from_hex(root)
175        } else {
176            Err(Error::InvalidData(
177                "note hash tree root not found in block header".into(),
178            ))
179        }
180    }
181
182    /// Get the master secret key (sk_m) for key verification in the kernel.
183    pub async fn get_master_secret_key(&self, pk_hash: &Fr) -> Result<Option<Fr>, Error> {
184        self.key_store.get_secret_key(pk_hash).await
185    }
186
187    /// Get block hash membership witness in the archive tree.
188    pub async fn get_block_hash_membership_witness(
189        &self,
190        block_hash: &Fr,
191    ) -> Result<Option<serde_json::Value>, Error> {
192        self.node
193            .get_block_hash_membership_witness(0, block_hash)
194            .await
195    }
196
197    /// Get updated class ID hints (public data witnesses for class ID updates).
198    pub async fn get_updated_class_id_hints(
199        &self,
200        address: &AztecAddress,
201    ) -> Result<serde_json::Value, Error> {
202        Err(Error::InvalidData(format!(
203            "updated class-id hints for contract {address} are not implemented yet"
204        )))
205    }
206
207    /// Get debug function name from selector (for error messages).
208    pub async fn get_debug_function_name(
209        &self,
210        contract_address: &AztecAddress,
211        function_selector: &FunctionSelector,
212    ) -> Result<Option<String>, Error> {
213        if let Some(instance) = self.contract_store.get_instance(contract_address).await? {
214            if let Some(artifact) = self
215                .contract_store
216                .get_artifact(&instance.inner.current_contract_class_id)
217                .await?
218            {
219                for func in &artifact.functions {
220                    if let Some(ref sel) = func.selector {
221                        if sel == function_selector {
222                            return Ok(Some(func.name.clone()));
223                        }
224                    }
225                }
226            }
227        }
228        Ok(None)
229    }
230
231    /// Get the block hash used for consistent state reads.
232    pub fn block_hash(&self) -> &Fr {
233        &self.block_hash
234    }
235}
236
237fn extract_packed_public_bytecode(artifact: &ContractArtifact) -> Vec<u8> {
238    // Only public_dispatch carries the packed bytecode (mirrors TS retainBytecode filter).
239    artifact
240        .functions
241        .iter()
242        .find(|f| {
243            f.function_type == aztec_core::abi::FunctionType::Public && f.name == "public_dispatch"
244        })
245        .and_then(|f| f.bytecode.as_deref())
246        .map(|bc| decode_bytecode(bc))
247        .unwrap_or_default()
248}
249
250fn decode_bytecode(encoded: &str) -> Vec<u8> {
251    let Some(hex) = encoded.strip_prefix("0x") else {
252        return Vec::new();
253    };
254    let mut bytes = Vec::with_capacity(hex.len() / 2);
255    let mut chars = hex.as_bytes().chunks_exact(2);
256    for pair in &mut chars {
257        if let Ok(pair_str) = std::str::from_utf8(pair) {
258            if let Ok(byte) = u8::from_str_radix(pair_str, 16) {
259                bytes.push(byte);
260            } else {
261                return Vec::new();
262            }
263        } else {
264            return Vec::new();
265        }
266    }
267    if !chars.remainder().is_empty() {
268        return Vec::new();
269    }
270    bytes
271}