Skip to main content

aztec_core/
hash.rs

1//! Poseidon2 hash functions for the Aztec protocol.
2//!
3//! Provides `poseidon2_hash_with_separator` and derived functions that mirror the
4//! TypeScript SDK's hashing utilities.
5
6use sha2::{Digest, Sha256};
7
8use crate::abi::{
9    encode_arguments, AbiValue, ContractArtifact, FunctionArtifact, FunctionSelector, FunctionType,
10};
11use crate::constants::{self, domain_separator};
12use crate::grumpkin;
13use crate::tx::FunctionCall;
14use crate::types::{AztecAddress, ContractInstance, Fq, Fr, PublicKeys};
15use crate::Error;
16
17/// Compute a Poseidon2 sponge hash over `inputs`.
18///
19/// Uses a rate-3 / capacity-1 sponge with the standard Aztec IV construction:
20/// `state[3] = len * 2^64`.
21///
22/// This matches barretenberg's `poseidon2_hash` and the TS SDK's `poseidon2Hash`.
23pub fn poseidon2_hash(inputs: &[Fr]) -> Fr {
24    use ark_bn254::Fr as ArkFr;
25    use taceo_poseidon2::bn254::t4::permutation;
26
27    const RATE: usize = 3;
28
29    // IV: capacity element = input_length * 2^64
30    let two_pow_64 = ArkFr::from(1u64 << 32) * ArkFr::from(1u64 << 32);
31    let iv = ArkFr::from(inputs.len() as u64) * two_pow_64;
32
33    let mut state: [ArkFr; 4] = [ArkFr::from(0u64), ArkFr::from(0u64), ArkFr::from(0u64), iv];
34    let mut cache = [ArkFr::from(0u64); RATE];
35    let mut cache_size = 0usize;
36
37    for input in inputs {
38        if cache_size == RATE {
39            for i in 0..RATE {
40                state[i] += cache[i];
41            }
42            cache = [ArkFr::from(0u64); RATE];
43            cache_size = 0;
44            state = permutation(&state);
45        }
46        cache[cache_size] = input.0;
47        cache_size += 1;
48    }
49
50    // Absorb remaining cache
51    for i in 0..cache_size {
52        state[i] += cache[i];
53    }
54    state = permutation(&state);
55
56    Fr(state[0])
57}
58
59/// Compute a Poseidon2 hash over raw bytes using the same chunking as Aztec's
60/// `poseidon2HashBytes`.
61///
62/// Bytes are split into 31-byte chunks, each chunk is placed into a 32-byte
63/// buffer, reversed, and then interpreted as a field element before hashing.
64pub(crate) fn poseidon2_hash_bytes(bytes: &[u8]) -> Fr {
65    if bytes.is_empty() {
66        return poseidon2_hash(&[]);
67    }
68
69    let inputs = bytes
70        .chunks(31)
71        .map(|chunk| {
72            let mut field_bytes = [0u8; 32];
73            field_bytes[..chunk.len()].copy_from_slice(chunk);
74            field_bytes.reverse();
75            Fr::from(field_bytes)
76        })
77        .collect::<Vec<_>>();
78
79    poseidon2_hash(&inputs)
80}
81
82/// Compute a Poseidon2 hash of `inputs` with a domain separator prepended.
83///
84/// Mirrors the TS `poseidon2HashWithSeparator(args, separator)`.
85pub fn poseidon2_hash_with_separator(inputs: &[Fr], separator: u32) -> Fr {
86    poseidon2_hash_with_separator_field(inputs, Fr::from(u64::from(separator)))
87}
88
89/// Compute a Poseidon2 hash of `inputs` with a full field-element domain separator prepended.
90pub fn poseidon2_hash_with_separator_field(inputs: &[Fr], separator: Fr) -> Fr {
91    let mut full_input = Vec::with_capacity(1 + inputs.len());
92    full_input.push(separator);
93    full_input.extend_from_slice(inputs);
94    poseidon2_hash(&full_input)
95}
96
97/// Hash a secret for use in L1-L2 message flow and TransparentNote.
98///
99/// `secret_hash = poseidon2([secret], SECRET_HASH)`
100///
101/// Mirrors TS `computeSecretHash(secret)`.
102pub fn compute_secret_hash(secret: &Fr) -> Fr {
103    poseidon2_hash_with_separator(&[*secret], domain_separator::SECRET_HASH)
104}
105
106/// Compute the nullifier for an L1-to-L2 message consumption.
107///
108/// `inner = poseidon2([message_hash, secret], MESSAGE_NULLIFIER)`
109/// `result = poseidon2([contract, inner], SILOED_NULLIFIER)`
110///
111/// Mirrors TS `computeL1ToL2MessageNullifier(contract, messageHash, secret)`.
112pub fn compute_l1_to_l2_message_nullifier(
113    contract: &AztecAddress,
114    message_hash: &Fr,
115    secret: &Fr,
116) -> Fr {
117    let inner = poseidon2_hash_with_separator(
118        &[*message_hash, *secret],
119        domain_separator::MESSAGE_NULLIFIER,
120    );
121    silo_nullifier(contract, &inner)
122}
123
124/// Compute an L2-to-L1 message hash.
125///
126/// `sha256_to_field([l2_sender, rollup_version, l1_recipient, chain_id, content])`
127///
128/// Mirrors TS `computeL2ToL1MessageHash(...)`.
129pub fn compute_l2_to_l1_message_hash(
130    l2_sender: &AztecAddress,
131    l1_recipient: &crate::types::EthAddress,
132    content: &Fr,
133    rollup_version: &Fr,
134    chain_id: &Fr,
135) -> Fr {
136    let mut data = Vec::with_capacity(5 * 32);
137    data.extend_from_slice(&l2_sender.0.to_be_bytes());
138    data.extend_from_slice(&rollup_version.to_be_bytes());
139    // EthAddress is 20 bytes, left-pad to 32
140    let mut eth_bytes = [0u8; 32];
141    eth_bytes[12..32].copy_from_slice(&l1_recipient.0);
142    data.extend_from_slice(&eth_bytes);
143    data.extend_from_slice(&chain_id.to_be_bytes());
144    data.extend_from_slice(&content.to_be_bytes());
145    sha256_to_field(&data)
146}
147
148/// Hash function arguments using Poseidon2 with the `FUNCTION_ARGS` separator.
149///
150/// Returns `Fr::zero()` if `args` is empty.
151///
152/// Mirrors TS `computeVarArgsHash(args)`.
153pub fn compute_var_args_hash(args: &[Fr]) -> Fr {
154    if args.is_empty() {
155        return Fr::zero();
156    }
157    poseidon2_hash_with_separator(args, domain_separator::FUNCTION_ARGS)
158}
159
160/// Hash public calldata using Poseidon2 with the `PUBLIC_CALLDATA` separator.
161///
162/// Mirrors TS `computeCalldataHash(calldata)`.
163pub fn compute_calldata_hash(calldata: &[Fr]) -> Fr {
164    poseidon2_hash_with_separator(calldata, domain_separator::PUBLIC_CALLDATA)
165}
166
167// ---------------------------------------------------------------------------
168// Kernel hash functions (silo, unique note hash, etc.)
169// ---------------------------------------------------------------------------
170
171/// Silo a note hash with a contract address.
172///
173/// Mirrors TS `siloNoteHash(contract, noteHash)`.
174pub fn silo_note_hash(contract_address: &AztecAddress, note_hash: &Fr) -> Fr {
175    poseidon2_hash_with_separator(
176        &[contract_address.0, *note_hash],
177        domain_separator::SILOED_NOTE_HASH,
178    )
179}
180
181/// Silo a nullifier with a contract address.
182///
183/// Mirrors TS `siloNullifier(contract, innerNullifier)`.
184pub fn silo_nullifier(contract_address: &AztecAddress, inner_nullifier: &Fr) -> Fr {
185    poseidon2_hash_with_separator(
186        &[contract_address.0, *inner_nullifier],
187        domain_separator::SILOED_NULLIFIER,
188    )
189}
190
191/// Compute the protocol nullifier from a tx request hash.
192///
193/// Mirrors TS `computeProtocolNullifier(txRequestHash)`.
194pub fn compute_protocol_nullifier(tx_request_hash: &Fr) -> Fr {
195    silo_nullifier(
196        &constants::protocol_contract_address::null_msg_sender(),
197        tx_request_hash,
198    )
199}
200
201/// Compute a unique note hash from its nonce and siloed hash.
202///
203/// Mirrors TS `computeUniqueNoteHash(nonce, siloedNoteHash)`.
204pub fn compute_unique_note_hash(nonce: &Fr, siloed_note_hash: &Fr) -> Fr {
205    poseidon2_hash_with_separator(
206        &[*nonce, *siloed_note_hash],
207        domain_separator::UNIQUE_NOTE_HASH,
208    )
209}
210
211/// Compute the nonce for a note hash from the first nullifier and index.
212///
213/// Mirrors TS `computeNoteHashNonce(nullifierZero, noteHashIndex)`.
214pub fn compute_note_hash_nonce(first_nullifier: &Fr, note_hash_index: usize) -> Fr {
215    poseidon2_hash_with_separator(
216        &[*first_nullifier, Fr::from(note_hash_index as u64)],
217        domain_separator::NOTE_HASH_NONCE,
218    )
219}
220
221/// Silo the first field of a private log with a contract address.
222///
223/// Mirrors TS `computeSiloedPrivateLogFirstField(contract, field)`.
224pub fn compute_siloed_private_log_first_field(contract_address: &AztecAddress, field: &Fr) -> Fr {
225    poseidon2_hash_with_separator(
226        &[contract_address.0, *field],
227        domain_separator::PRIVATE_LOG_FIRST_FIELD,
228    )
229}
230
231/// Compute the inner authwit hash — the "intent" before siloing with consumer.
232///
233/// `args` is typically `[caller, selector, args_hash]`.
234/// Uses Poseidon2 with `AUTHWIT_INNER` domain separator.
235///
236/// Mirrors TS `computeInnerAuthWitHash(args)`.
237pub fn compute_inner_auth_wit_hash(args: &[Fr]) -> Fr {
238    poseidon2_hash_with_separator(args, domain_separator::AUTHWIT_INNER)
239}
240
241/// Compute the outer authwit hash — the value the approver signs.
242///
243/// Combines consumer address, chain ID, protocol version, and inner hash.
244/// Uses Poseidon2 with `AUTHWIT_OUTER` domain separator.
245///
246/// Mirrors TS `computeOuterAuthWitHash(consumer, chainId, version, innerHash)`.
247pub fn compute_outer_auth_wit_hash(
248    consumer: &AztecAddress,
249    chain_id: &Fr,
250    version: &Fr,
251    inner_hash: &Fr,
252) -> Fr {
253    poseidon2_hash_with_separator(
254        &[consumer.0, *chain_id, *version, *inner_hash],
255        domain_separator::AUTHWIT_OUTER,
256    )
257}
258
259/// Compute the protocol contracts hash from the known protocol contract addresses.
260///
261/// Mirrors TS `ProtocolContractAddress.computeProtocolContractsHash()` which hashes
262/// the five canonical protocol contract addresses (1..5) with Poseidon2.
263pub fn compute_protocol_contracts_hash() -> Fr {
264    // Upstream `ProtocolContractsList` for Aztec 4.1.3.
265    // These are the derived protocol contract addresses, not the canonical
266    // public interface addresses 1..=6.
267    let derived_addresses = [
268        Fr::from_hex("0x139f8eb6d6e7e7a7c0322c3b7558687a7e24201f11bf2c4cb2fe56c18d363695")
269            .expect("valid protocol contract address"),
270        Fr::from_hex("0x1254246c88aca5a66fa66f3aa78c408a698ebca3b713120497c7555dfc718592")
271            .expect("valid protocol contract address"),
272        Fr::from_hex("0x14d670efa326a07b99777b01fb706427ca776095246569150f2a3f17a7d4dc66")
273            .expect("valid protocol contract address"),
274        Fr::from_hex("0x230d0b47ba6d5ed99afb89d584f32ff33438b64f51000f252a140cf995781628")
275            .expect("valid protocol contract address"),
276        Fr::from_hex("0x204913186c0dd70015d05bf9100a12e31ccb7cc2527aacdfae0c19ad6439fcf4")
277            .expect("valid protocol contract address"),
278        Fr::from_hex("0x1198142fd84a58c0ab22d5fde371ce527042db49487e05206a326ad154952ac8")
279            .expect("valid protocol contract address"),
280        Fr::zero(),
281        Fr::zero(),
282        Fr::zero(),
283        Fr::zero(),
284        Fr::zero(),
285    ];
286
287    poseidon2_hash_with_separator(&derived_addresses, domain_separator::PROTOCOL_CONTRACTS)
288}
289
290/// Flatten ABI values into their field element representation.
291///
292/// Handles the common types used in authwit scenarios: `Field`, `Boolean`,
293/// `Integer`, `Array`, `Struct`, and `Tuple`. Strings are encoded as
294/// one field element per byte.
295pub fn abi_values_to_fields(args: &[AbiValue]) -> Vec<Fr> {
296    let mut fields = Vec::new();
297    for arg in args {
298        flatten_abi_value(arg, &mut fields);
299    }
300    fields
301}
302
303fn flatten_abi_value(value: &AbiValue, out: &mut Vec<Fr>) {
304    match value {
305        AbiValue::Field(f) => out.push(*f),
306        AbiValue::Boolean(b) => out.push(if *b { Fr::one() } else { Fr::zero() }),
307        AbiValue::Integer(i) => {
308            // Integers are encoded as unsigned field elements.
309            // Negative values are not expected in authwit args.
310            out.push(Fr::from(*i as u64));
311        }
312        AbiValue::Array(items) => {
313            for item in items {
314                flatten_abi_value(item, out);
315            }
316        }
317        AbiValue::String(s) => {
318            for byte in s.bytes() {
319                out.push(Fr::from(u64::from(byte)));
320            }
321        }
322        AbiValue::Struct(map) => {
323            // BTreeMap iterates in key order (deterministic).
324            for value in map.values() {
325                flatten_abi_value(value, out);
326            }
327        }
328        AbiValue::Tuple(items) => {
329            for item in items {
330                flatten_abi_value(item, out);
331            }
332        }
333    }
334}
335
336/// Compute the inner authwit hash from a caller address and a function call.
337///
338/// Computes `computeInnerAuthWitHash([caller, call.selector, varArgsHash(call.args)])`.
339///
340/// Mirrors TS `computeInnerAuthWitHashFromAction(caller, action)`.
341pub fn compute_inner_auth_wit_hash_from_action(caller: &AztecAddress, call: &FunctionCall) -> Fr {
342    let args_as_fields = abi_values_to_fields(&call.args);
343    let args_hash = compute_var_args_hash(&args_as_fields);
344    compute_inner_auth_wit_hash(&[caller.0, call.selector.to_field(), args_hash])
345}
346
347/// Chain identification information.
348///
349/// This is defined in `aztec-core` so that hash functions can use it
350/// without creating a circular dependency with `aztec-wallet`.
351#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
352#[serde(rename_all = "camelCase")]
353pub struct ChainInfo {
354    /// The L2 chain ID.
355    pub chain_id: Fr,
356    /// The rollup protocol version.
357    pub version: Fr,
358}
359
360/// Either a raw message hash, a structured call intent, or a pre-computed
361/// inner hash with its consumer address.
362///
363/// Mirrors the TS distinction between `Fr`, `CallIntent`, and `IntentInnerHash`.
364#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
365#[serde(tag = "type", rename_all = "snake_case")]
366pub enum MessageHashOrIntent {
367    /// A raw message hash (already computed outer hash).
368    Hash {
369        /// The hash value.
370        hash: Fr,
371    },
372    /// A structured call intent.
373    Intent {
374        /// The caller requesting authorization.
375        caller: AztecAddress,
376        /// The function call to authorize.
377        call: FunctionCall,
378    },
379    /// A pre-computed inner hash with consumer address.
380    ///
381    /// Used when the inner hash is already known but the outer hash
382    /// (which includes chain info) still needs to be computed.
383    InnerHash {
384        /// The consumer contract address.
385        consumer: AztecAddress,
386        /// The inner hash value.
387        inner_hash: Fr,
388    },
389}
390
391/// Compute the full authwit message hash from an intent and chain info.
392///
393/// For `MessageHashOrIntent::Hash` — returns the hash directly.
394/// For `MessageHashOrIntent::Intent { caller, call }`:
395///   1. `inner_hash = compute_inner_auth_wit_hash_from_action(caller, call)`
396///   2. `consumer = call.to` (the contract being called)
397///   3. `outer_hash = compute_outer_auth_wit_hash(consumer, chain_id, version, inner_hash)`
398///
399/// For `MessageHashOrIntent::InnerHash { consumer, inner_hash }`:
400///   1. `outer_hash = compute_outer_auth_wit_hash(consumer, chain_id, version, inner_hash)`
401///
402/// Mirrors TS `computeAuthWitMessageHash(intent, metadata)`.
403pub fn compute_auth_wit_message_hash(intent: &MessageHashOrIntent, chain_info: &ChainInfo) -> Fr {
404    match intent {
405        MessageHashOrIntent::Hash { hash } => *hash,
406        MessageHashOrIntent::Intent { caller, call } => {
407            let inner_hash = compute_inner_auth_wit_hash_from_action(caller, call);
408            compute_outer_auth_wit_hash(
409                &call.to,
410                &chain_info.chain_id,
411                &chain_info.version,
412                &inner_hash,
413            )
414        }
415        MessageHashOrIntent::InnerHash {
416            consumer,
417            inner_hash,
418        } => compute_outer_auth_wit_hash(
419            consumer,
420            &chain_info.chain_id,
421            &chain_info.version,
422            inner_hash,
423        ),
424    }
425}
426
427// ---------------------------------------------------------------------------
428// Deployment hash primitives
429// ---------------------------------------------------------------------------
430
431/// Compute the initialization hash for a contract deployment.
432///
433/// Returns `Fr::zero()` if `init_fn` is `None` (no constructor).
434///
435/// Formula: `poseidon2_hash_with_separator([selector, args_hash], INITIALIZER)`
436pub fn compute_initialization_hash(
437    init_fn: Option<&FunctionArtifact>,
438    args: &[AbiValue],
439) -> Result<Fr, Error> {
440    match init_fn {
441        None => Ok(Fr::zero()),
442        Some(func) => {
443            let selector = func.selector.unwrap_or_else(|| {
444                FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
445            });
446            let encoded_args = encode_arguments(func, args)?;
447            let args_hash = compute_var_args_hash(&encoded_args);
448            Ok(poseidon2_hash_with_separator(
449                &[selector.to_field(), args_hash],
450                domain_separator::INITIALIZER,
451            ))
452        }
453    }
454}
455
456/// Compute initialization hash from pre-encoded selector and args.
457pub fn compute_initialization_hash_from_encoded(selector: Fr, encoded_args: &[Fr]) -> Fr {
458    let args_hash = compute_var_args_hash(encoded_args);
459    poseidon2_hash_with_separator(&[selector, args_hash], domain_separator::INITIALIZER)
460}
461
462// ---------------------------------------------------------------------------
463// Contract class ID computation (Step 5.5)
464// ---------------------------------------------------------------------------
465
466/// Compute the root of the private functions Merkle tree.
467///
468/// Each leaf = `poseidon2_hash_with_separator([selector, vk_hash], PRIVATE_FUNCTION_LEAF)`.
469/// Tree height = `FUNCTION_TREE_HEIGHT` (7).
470pub fn compute_private_functions_root(private_functions: &mut [(FunctionSelector, Fr)]) -> Fr {
471    let tree_height = constants::FUNCTION_TREE_HEIGHT;
472    let num_leaves = 1usize << tree_height; // 128
473
474    // Sort by selector bytes (big-endian u32 value).
475    private_functions.sort_by_key(|(sel, _)| u32::from_be_bytes(sel.0));
476
477    // Compute leaves.
478    let zero_leaf = poseidon2_hash(&[Fr::zero(), Fr::zero()]);
479    let mut leaves: Vec<Fr> = Vec::with_capacity(num_leaves);
480    for (sel, vk_hash) in private_functions.iter() {
481        let leaf = poseidon2_hash_with_separator(
482            &[sel.to_field(), *vk_hash],
483            domain_separator::PRIVATE_FUNCTION_LEAF,
484        );
485        leaves.push(leaf);
486    }
487    // Pad remaining leaves with zeros.
488    leaves.resize(num_leaves, zero_leaf);
489
490    // Build Merkle tree bottom-up using Poseidon2 for internal nodes.
491    poseidon_merkle_root(&leaves)
492}
493
494/// Build a binary Merkle tree root from leaves using Poseidon2.
495fn poseidon_merkle_root(leaves: &[Fr]) -> Fr {
496    if leaves.is_empty() {
497        return Fr::zero();
498    }
499    if leaves.len() == 1 {
500        return leaves[0];
501    }
502
503    let mut current = leaves.to_vec();
504    while current.len() > 1 {
505        let mut next = Vec::with_capacity(current.len().div_ceil(2));
506        for chunk in current.chunks(2) {
507            let left = chunk[0];
508            let right = if chunk.len() > 1 {
509                chunk[1]
510            } else {
511                Fr::zero()
512            };
513            next.push(poseidon2_hash(&[left, right]));
514        }
515        current = next;
516    }
517    current[0]
518}
519
520fn sha256_merkle_root(leaves: &[Fr]) -> Fr {
521    if leaves.is_empty() {
522        return Fr::zero();
523    }
524    if leaves.len() == 1 {
525        return leaves[0];
526    }
527
528    let mut current = leaves.to_vec();
529    while current.len() > 1 {
530        let mut next = Vec::with_capacity(current.len().div_ceil(2));
531        for chunk in current.chunks(2) {
532            let left = chunk[0].to_be_bytes();
533            let right = chunk.get(1).unwrap_or(&Fr::zero()).to_be_bytes();
534            next.push(sha256_to_field(
535                &[left.as_slice(), right.as_slice()].concat(),
536            ));
537        }
538        current = next;
539    }
540    current[0]
541}
542
543/// Compute the SHA256 hash of a byte slice, returning the result as an `Fr`.
544///
545/// Matches the TS `sha256ToField` which truncates to 31 bytes and prepends a
546/// zero byte, guaranteeing the result fits in the BN254 scalar field.
547/// Public version of `sha256_to_field` for cross-crate use.
548pub fn sha256_to_field_pub(data: &[u8]) -> Fr {
549    sha256_to_field(data)
550}
551
552fn sha256_to_field(data: &[u8]) -> Fr {
553    let hash = Sha256::digest(data);
554    let mut bytes = [0u8; 32];
555    // Take first 31 bytes of hash, prepend 0x00 — matches TS `truncateAndPad`.
556    bytes[1..].copy_from_slice(&hash[..31]);
557    Fr::from(bytes)
558}
559
560/// Compute the artifact hash for a contract.
561///
562/// Uses SHA256 for per-function bytecode/metadata hashing, then combines
563/// private and unconstrained function artifact tree roots with a metadata hash.
564pub fn compute_artifact_hash(artifact: &ContractArtifact) -> Fr {
565    let private_fn_tree_root = compute_artifact_function_tree_root(artifact, false);
566    let unconstrained_fn_tree_root = compute_artifact_function_tree_root(artifact, true);
567    let metadata_hash = compute_artifact_metadata_hash(artifact);
568
569    let mut data = Vec::new();
570    data.push(1u8);
571    data.extend_from_slice(&private_fn_tree_root.to_be_bytes());
572    data.extend_from_slice(&unconstrained_fn_tree_root.to_be_bytes());
573    data.extend_from_slice(&metadata_hash.to_be_bytes());
574    sha256_to_field(&data)
575}
576
577fn canonical_json_string(value: &serde_json::Value) -> String {
578    match value {
579        serde_json::Value::Null => "null".to_owned(),
580        serde_json::Value::Bool(boolean) => boolean.to_string(),
581        serde_json::Value::Number(number) => number.to_string(),
582        serde_json::Value::String(string) => {
583            serde_json::to_string(string).unwrap_or_else(|_| "\"\"".to_owned())
584        }
585        serde_json::Value::Array(items) => {
586            let inner = items
587                .iter()
588                .map(canonical_json_string)
589                .collect::<Vec<_>>()
590                .join(",");
591            format!("[{inner}]")
592        }
593        serde_json::Value::Object(map) => {
594            let mut entries = map.iter().collect::<Vec<_>>();
595            entries.sort_by(|(left, _), (right, _)| left.cmp(right));
596            let inner = entries
597                .into_iter()
598                .map(|(key, value)| {
599                    let key = serde_json::to_string(key).unwrap_or_else(|_| "\"\"".to_owned());
600                    format!("{key}:{}", canonical_json_string(value))
601                })
602                .collect::<Vec<_>>()
603                .join(",");
604            format!("{{{inner}}}")
605        }
606    }
607}
608
609fn decode_artifact_bytes(encoded: &str) -> Vec<u8> {
610    if let Some(hex) = encoded.strip_prefix("0x") {
611        return hex::decode(hex).unwrap_or_else(|_| encoded.as_bytes().to_vec());
612    }
613
614    use base64::Engine;
615    base64::engine::general_purpose::STANDARD
616        .decode(encoded)
617        .unwrap_or_else(|_| encoded.as_bytes().to_vec())
618}
619
620/// Compute the artifact function tree root for private or unconstrained functions.
621fn compute_artifact_function_tree_root(artifact: &ContractArtifact, unconstrained: bool) -> Fr {
622    let functions: Vec<&FunctionArtifact> = artifact
623        .functions
624        .iter()
625        .filter(|f| {
626            if unconstrained {
627                f.function_type == FunctionType::Utility || f.is_unconstrained == Some(true)
628            } else {
629                f.function_type == FunctionType::Private
630            }
631        })
632        .collect();
633
634    if functions.is_empty() {
635        return Fr::zero();
636    }
637
638    let leaves: Vec<Fr> = functions
639        .iter()
640        .map(|func| {
641            let selector = func.selector.unwrap_or_else(|| {
642                FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
643            });
644            let metadata_hash = compute_function_metadata_hash(func);
645            let bytecode_hash = compute_function_bytecode_hash(func);
646
647            let mut leaf_data = Vec::new();
648            leaf_data.push(1u8);
649            leaf_data.extend_from_slice(&selector.0);
650            leaf_data.extend_from_slice(&metadata_hash.to_be_bytes());
651            leaf_data.extend_from_slice(&bytecode_hash.to_be_bytes());
652            sha256_to_field(&leaf_data)
653        })
654        .collect();
655
656    let height = if leaves.len() <= 1 {
657        0
658    } else {
659        (leaves.len() as f64).log2().ceil() as usize
660    };
661    let num_leaves = 1usize << height;
662    let mut padded = leaves;
663    padded.resize(num_leaves.max(1), Fr::zero());
664    sha256_merkle_root(&padded)
665}
666
667/// Hash function metadata exactly as upstream does.
668fn compute_function_metadata_hash(func: &FunctionArtifact) -> Fr {
669    let metadata = serde_json::to_value(&func.return_types).unwrap_or(serde_json::Value::Null);
670    let serialized = canonical_json_string(&metadata);
671    sha256_to_field(serialized.as_bytes())
672}
673
674/// Hash function bytecode.
675fn compute_function_bytecode_hash(func: &FunctionArtifact) -> Fr {
676    match &func.bytecode {
677        Some(bc) if !bc.is_empty() => sha256_to_field(&decode_artifact_bytes(bc)),
678        _ => Fr::zero(),
679    }
680}
681
682/// Hash artifact-level metadata.
683fn compute_artifact_metadata_hash(artifact: &ContractArtifact) -> Fr {
684    let mut metadata = serde_json::Map::new();
685    metadata.insert(
686        "name".to_owned(),
687        serde_json::Value::String(artifact.name.clone()),
688    );
689    if let Some(outputs) = &artifact.outputs {
690        metadata.insert("outputs".to_owned(), outputs.clone());
691    }
692    let serialized = canonical_json_string(&serde_json::Value::Object(metadata));
693    sha256_to_field(serialized.as_bytes())
694}
695
696/// Compute the commitment to packed public bytecode.
697///
698/// Encodes bytecode as field elements (31 bytes each) and hashes with Poseidon2.
699pub fn compute_public_bytecode_commitment(packed_bytecode: &[u8]) -> Fr {
700    let fields = crate::abi::buffer_as_fields(
701        packed_bytecode,
702        constants::MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
703    )
704    .expect("packed bytecode exceeds maximum field count");
705    let byte_length = fields[0].to_usize() as u64;
706    let length_in_fields = byte_length.div_ceil(31) as usize;
707
708    let separator = Fr::from(u64::from(domain_separator::PUBLIC_BYTECODE) + (byte_length << 32));
709    poseidon2_hash_with_separator_field(&fields[1..1 + length_in_fields], separator)
710}
711
712/// Compute the contract class ID from its components.
713///
714/// `class_id = poseidon2_hash_with_separator([artifact_hash, private_functions_root, public_bytecode_commitment], CONTRACT_CLASS_ID)`
715pub fn compute_contract_class_id(
716    artifact_hash: Fr,
717    private_functions_root: Fr,
718    public_bytecode_commitment: Fr,
719) -> Fr {
720    poseidon2_hash_with_separator(
721        &[
722            artifact_hash,
723            private_functions_root,
724            public_bytecode_commitment,
725        ],
726        domain_separator::CONTRACT_CLASS_ID,
727    )
728}
729
730/// Compute contract class ID directly from a `ContractArtifact`.
731pub fn compute_contract_class_id_from_artifact(artifact: &ContractArtifact) -> Result<Fr, Error> {
732    let artifact_hash = compute_artifact_hash(artifact);
733    let private_fns_root = compute_private_functions_root_from_artifact(artifact)?;
734    let public_bytecode = extract_packed_public_bytecode(artifact);
735    let public_bytecode_commitment = compute_public_bytecode_commitment(&public_bytecode);
736    Ok(compute_contract_class_id(
737        artifact_hash,
738        private_fns_root,
739        public_bytecode_commitment,
740    ))
741}
742
743/// Extract private functions from an artifact and compute the root.
744pub fn compute_private_functions_root_from_artifact(
745    artifact: &ContractArtifact,
746) -> Result<Fr, Error> {
747    let mut private_fns: Vec<(FunctionSelector, Fr)> = artifact
748        .functions
749        .iter()
750        .filter(|f| f.function_type == FunctionType::Private)
751        .map(|f| {
752            let selector = f.selector.unwrap_or_else(|| {
753                FunctionSelector::from_name_and_parameters(&f.name, &f.parameters)
754            });
755            let vk_hash = f.verification_key_hash.unwrap_or(Fr::zero());
756            (selector, vk_hash)
757        })
758        .collect();
759
760    Ok(compute_private_functions_root(&mut private_fns))
761}
762
763/// Extract packed public bytecode from an artifact.
764fn extract_packed_public_bytecode(artifact: &ContractArtifact) -> Vec<u8> {
765    // Only public_dispatch carries the packed bytecode (mirrors TS retainBytecode filter).
766    artifact
767        .functions
768        .iter()
769        .find(|f| f.function_type == FunctionType::Public && f.name == "public_dispatch")
770        .and_then(|f| f.bytecode.as_ref())
771        .map(|bc| decode_artifact_bytes(bc))
772        .unwrap_or_default()
773}
774
775// ---------------------------------------------------------------------------
776// Contract address derivation (Step 5.6)
777// ---------------------------------------------------------------------------
778
779/// Compute the salted initialization hash.
780///
781/// `salted = poseidon2_hash_with_separator([salt, initialization_hash, deployer], PARTIAL_ADDRESS)`
782pub fn compute_salted_initialization_hash(
783    salt: Fr,
784    initialization_hash: Fr,
785    deployer: AztecAddress,
786) -> Fr {
787    poseidon2_hash_with_separator(
788        &[salt, initialization_hash, deployer.0],
789        domain_separator::PARTIAL_ADDRESS,
790    )
791}
792
793/// Compute the partial address from class ID and salted init hash.
794///
795/// `partial = poseidon2_hash_with_separator([class_id, salted_init_hash], PARTIAL_ADDRESS)`
796pub fn compute_partial_address(
797    original_contract_class_id: Fr,
798    salted_initialization_hash: Fr,
799) -> Fr {
800    poseidon2_hash_with_separator(
801        &[original_contract_class_id, salted_initialization_hash],
802        domain_separator::PARTIAL_ADDRESS,
803    )
804}
805
806/// Compute an Aztec address from public keys and a partial address.
807///
808/// Algorithm:
809///   1. `preaddress = poseidon2([public_keys_hash, partial_address], CONTRACT_ADDRESS_V1)`
810///   2. `address_point = (Fq(preaddress) * G) + ivpk_m`
811///   3. `address = address_point.x`
812pub fn compute_address(
813    public_keys: &PublicKeys,
814    partial_address: &Fr,
815) -> Result<AztecAddress, Error> {
816    let public_keys_hash = public_keys.hash();
817    let preaddress = poseidon2_hash_with_separator(
818        &[public_keys_hash, *partial_address],
819        domain_separator::CONTRACT_ADDRESS_V1,
820    );
821
822    // Convert Fr preaddress to Fq for Grumpkin scalar multiplication
823    // (matches TS: `new Fq(preaddress.toBigInt())`)
824    let preaddress_fq = Fq::from_be_bytes_mod_order(&preaddress.to_be_bytes());
825
826    let g = grumpkin::generator();
827    let preaddress_point = grumpkin::scalar_mul(&preaddress_fq, &g);
828
829    let ivpk_m = &public_keys.master_incoming_viewing_public_key;
830    // Point::is_zero() already checks !is_infinite, so no extra guard needed.
831    let address_point = if ivpk_m.is_zero() {
832        preaddress_point
833    } else {
834        grumpkin::point_add(&preaddress_point, ivpk_m)
835    };
836
837    if address_point.is_infinite {
838        return Err(Error::InvalidData(
839            "address derivation resulted in point at infinity".to_owned(),
840        ));
841    }
842
843    Ok(AztecAddress(address_point.x))
844}
845
846/// Compute the contract address from a `ContractInstance`.
847///
848/// ```text
849/// address = (poseidon2_hash_with_separator(
850///     [public_keys_hash, partial_address],
851///     CONTRACT_ADDRESS_V1
852/// ) * G + ivpk_m).x
853/// ```
854pub fn compute_contract_address_from_instance(
855    instance: &ContractInstance,
856) -> Result<AztecAddress, Error> {
857    let salted_init_hash = compute_salted_initialization_hash(
858        instance.salt,
859        instance.initialization_hash,
860        instance.deployer,
861    );
862    let partial_address =
863        compute_partial_address(instance.original_contract_class_id, salted_init_hash);
864
865    compute_address(&instance.public_keys, &partial_address)
866}
867
868#[cfg(test)]
869#[allow(clippy::expect_used, clippy::panic)]
870mod tests {
871    use super::*;
872    use crate::abi::{buffer_as_fields, FunctionSelector, FunctionType};
873
874    #[test]
875    fn var_args_hash_empty_returns_zero() {
876        assert_eq!(compute_var_args_hash(&[]), Fr::zero());
877    }
878
879    #[test]
880    fn poseidon2_hash_known_vector() {
881        // Test vector: hash of [1] should produce a known result.
882        // This validates the sponge construction matches barretenberg.
883        let result = poseidon2_hash(&[Fr::from(1u64)]);
884        let expected =
885            Fr::from_hex("0x168758332d5b3e2d13be8048c8011b454590e06c44bce7f702f09103eef5a373")
886                .expect("valid hex");
887        assert_eq!(
888            result, expected,
889            "Poseidon2 hash of [1] must match barretenberg test vector"
890        );
891    }
892
893    #[test]
894    fn poseidon2_hash_with_separator_prepends_separator() {
895        // hash_with_separator([a, b], sep) == hash([Fr(sep), a, b])
896        let a = Fr::from(10u64);
897        let b = Fr::from(20u64);
898        let sep = 42u32;
899
900        let result = poseidon2_hash_with_separator(&[a, b], sep);
901        let manual = poseidon2_hash(&[Fr::from(u64::from(sep)), a, b]);
902        assert_eq!(result, manual);
903    }
904
905    #[test]
906    fn secret_hash_uses_correct_separator() {
907        let secret = Fr::from(42u64);
908        let result = compute_secret_hash(&secret);
909        let expected = poseidon2_hash_with_separator(&[secret], domain_separator::SECRET_HASH);
910        assert_eq!(result, expected);
911        // Must be non-zero for a non-zero secret
912        assert!(!result.is_zero());
913    }
914
915    #[test]
916    fn secret_hash_is_deterministic() {
917        let secret = Fr::from(12345u64);
918        let h1 = compute_secret_hash(&secret);
919        let h2 = compute_secret_hash(&secret);
920        assert_eq!(h1, h2);
921    }
922
923    #[test]
924    fn var_args_hash_single_element() {
925        let result = compute_var_args_hash(&[Fr::from(42u64)]);
926        // Should be poseidon2_hash([FUNCTION_ARGS_SEP, 42])
927        let expected =
928            poseidon2_hash_with_separator(&[Fr::from(42u64)], domain_separator::FUNCTION_ARGS);
929        assert_eq!(result, expected);
930    }
931
932    #[test]
933    fn inner_auth_wit_hash_uses_correct_separator() {
934        let args = [Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)];
935        let result = compute_inner_auth_wit_hash(&args);
936        let expected = poseidon2_hash_with_separator(&args, domain_separator::AUTHWIT_INNER);
937        assert_eq!(result, expected);
938    }
939
940    #[test]
941    fn outer_auth_wit_hash_uses_correct_separator() {
942        let consumer = AztecAddress(Fr::from(100u64));
943        let chain_id = Fr::from(31337u64);
944        let version = Fr::from(1u64);
945        let inner_hash = Fr::from(999u64);
946
947        let result = compute_outer_auth_wit_hash(&consumer, &chain_id, &version, &inner_hash);
948        let expected = poseidon2_hash_with_separator(
949            &[consumer.0, chain_id, version, inner_hash],
950            domain_separator::AUTHWIT_OUTER,
951        );
952        assert_eq!(result, expected);
953    }
954
955    #[test]
956    fn inner_auth_wit_hash_from_action() {
957        let caller = AztecAddress(Fr::from(1u64));
958        let call = FunctionCall {
959            to: AztecAddress(Fr::from(2u64)),
960            selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid"),
961            args: vec![AbiValue::Field(Fr::from(100u64))],
962            function_type: FunctionType::Private,
963            is_static: false,
964            hide_msg_sender: false,
965        };
966
967        let result = compute_inner_auth_wit_hash_from_action(&caller, &call);
968
969        // Manual computation
970        let args_hash = compute_var_args_hash(&[Fr::from(100u64)]);
971        let selector_field = call.selector.to_field();
972        let expected = compute_inner_auth_wit_hash(&[caller.0, selector_field, args_hash]);
973        assert_eq!(result, expected);
974    }
975
976    #[test]
977    fn auth_wit_message_hash_passthrough() {
978        let hash = Fr::from(42u64);
979        let chain_info = ChainInfo {
980            chain_id: Fr::from(31337u64),
981            version: Fr::from(1u64),
982        };
983        let result =
984            compute_auth_wit_message_hash(&MessageHashOrIntent::Hash { hash }, &chain_info);
985        assert_eq!(result, hash);
986    }
987
988    #[test]
989    fn auth_wit_message_hash_from_intent() {
990        let caller = AztecAddress(Fr::from(10u64));
991        let consumer = AztecAddress(Fr::from(20u64));
992        let call = FunctionCall {
993            to: consumer,
994            selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
995            args: vec![],
996            function_type: FunctionType::Private,
997            is_static: false,
998            hide_msg_sender: false,
999        };
1000        let chain_info = ChainInfo {
1001            chain_id: Fr::from(31337u64),
1002            version: Fr::from(1u64),
1003        };
1004
1005        let result = compute_auth_wit_message_hash(
1006            &MessageHashOrIntent::Intent {
1007                caller,
1008                call: call.clone(),
1009            },
1010            &chain_info,
1011        );
1012
1013        // Manual computation
1014        let inner = compute_inner_auth_wit_hash_from_action(&caller, &call);
1015        let expected = compute_outer_auth_wit_hash(
1016            &consumer,
1017            &chain_info.chain_id,
1018            &chain_info.version,
1019            &inner,
1020        );
1021        assert_eq!(result, expected);
1022    }
1023
1024    #[test]
1025    fn auth_wit_message_hash_from_inner_hash() {
1026        let consumer = AztecAddress(Fr::from(20u64));
1027        let inner_hash = Fr::from(999u64);
1028        let chain_info = ChainInfo {
1029            chain_id: Fr::from(31337u64),
1030            version: Fr::from(1u64),
1031        };
1032
1033        let result = compute_auth_wit_message_hash(
1034            &MessageHashOrIntent::InnerHash {
1035                consumer,
1036                inner_hash,
1037            },
1038            &chain_info,
1039        );
1040
1041        let expected = compute_outer_auth_wit_hash(
1042            &consumer,
1043            &chain_info.chain_id,
1044            &chain_info.version,
1045            &inner_hash,
1046        );
1047        assert_eq!(result, expected);
1048    }
1049
1050    #[test]
1051    fn abi_values_to_fields_basic_types() {
1052        let values = vec![
1053            AbiValue::Field(Fr::from(1u64)),
1054            AbiValue::Boolean(true),
1055            AbiValue::Boolean(false),
1056            AbiValue::Integer(42),
1057        ];
1058        let fields = abi_values_to_fields(&values);
1059        assert_eq!(fields.len(), 4);
1060        assert_eq!(fields[0], Fr::from(1u64));
1061        assert_eq!(fields[1], Fr::one());
1062        assert_eq!(fields[2], Fr::zero());
1063        assert_eq!(fields[3], Fr::from(42u64));
1064    }
1065
1066    #[test]
1067    fn abi_values_to_fields_nested() {
1068        let values = vec![AbiValue::Array(vec![
1069            AbiValue::Field(Fr::from(1u64)),
1070            AbiValue::Field(Fr::from(2u64)),
1071        ])];
1072        let fields = abi_values_to_fields(&values);
1073        assert_eq!(fields.len(), 2);
1074        assert_eq!(fields[0], Fr::from(1u64));
1075        assert_eq!(fields[1], Fr::from(2u64));
1076    }
1077
1078    #[test]
1079    fn message_hash_or_intent_serde_roundtrip() {
1080        let variants = vec![
1081            MessageHashOrIntent::Hash {
1082                hash: Fr::from(42u64),
1083            },
1084            MessageHashOrIntent::Intent {
1085                caller: AztecAddress(Fr::from(1u64)),
1086                call: FunctionCall {
1087                    to: AztecAddress(Fr::from(2u64)),
1088                    selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid"),
1089                    args: vec![],
1090                    function_type: FunctionType::Private,
1091                    is_static: false,
1092                    hide_msg_sender: false,
1093                },
1094            },
1095            MessageHashOrIntent::InnerHash {
1096                consumer: AztecAddress(Fr::from(3u64)),
1097                inner_hash: Fr::from(999u64),
1098            },
1099        ];
1100
1101        for variant in variants {
1102            let json = serde_json::to_string(&variant).expect("serialize");
1103            let decoded: MessageHashOrIntent = serde_json::from_str(&json).expect("deserialize");
1104            assert_eq!(decoded, variant);
1105        }
1106    }
1107
1108    #[test]
1109    fn chain_info_serde_roundtrip() {
1110        let info = ChainInfo {
1111            chain_id: Fr::from(31337u64),
1112            version: Fr::from(1u64),
1113        };
1114        let json = serde_json::to_string(&info).expect("serialize");
1115        let decoded: ChainInfo = serde_json::from_str(&json).expect("deserialize");
1116        assert_eq!(decoded, info);
1117    }
1118
1119    // -- Deployment hash tests --
1120
1121    #[test]
1122    fn initialization_hash_no_constructor_returns_zero() {
1123        let result = compute_initialization_hash(None, &[]).expect("no constructor");
1124        assert_eq!(result, Fr::zero());
1125    }
1126
1127    #[test]
1128    fn initialization_hash_with_constructor() {
1129        use crate::abi::AbiParameter;
1130        let func = FunctionArtifact {
1131            name: "constructor".to_owned(),
1132            function_type: FunctionType::Private,
1133            is_initializer: true,
1134            is_static: false,
1135            is_only_self: None,
1136            parameters: vec![AbiParameter {
1137                name: "admin".to_owned(),
1138                typ: crate::abi::AbiType::Field,
1139                visibility: None,
1140            }],
1141            return_types: vec![],
1142            error_types: None,
1143            selector: Some(FunctionSelector::from_hex("0xe5fb6c81").expect("valid")),
1144            bytecode: None,
1145            verification_key_hash: None,
1146            verification_key: None,
1147            custom_attributes: None,
1148            is_unconstrained: None,
1149            debug_symbols: None,
1150        };
1151        let args = vec![AbiValue::Field(Fr::from(42u64))];
1152        let result = compute_initialization_hash(Some(&func), &args).expect("init hash");
1153        assert_ne!(result, Fr::zero());
1154    }
1155
1156    #[test]
1157    fn initialization_hash_from_encoded() {
1158        let selector = Fr::from(12345u64);
1159        let args = vec![Fr::from(1u64), Fr::from(2u64)];
1160        let result = compute_initialization_hash_from_encoded(selector, &args);
1161        let args_hash = compute_var_args_hash(&args);
1162        let expected =
1163            poseidon2_hash_with_separator(&[selector, args_hash], domain_separator::INITIALIZER);
1164        assert_eq!(result, expected);
1165    }
1166
1167    #[test]
1168    fn private_functions_root_empty() {
1169        let root = compute_private_functions_root(&mut []);
1170        // Empty leaves all zero => root is the Merkle root of 128 zero leaves
1171        assert_ne!(root, Fr::zero()); // still a valid root, just all-zero tree
1172    }
1173
1174    #[test]
1175    fn contract_class_id_deterministic() {
1176        let artifact_hash = Fr::from(1u64);
1177        let root = Fr::from(2u64);
1178        let commitment = Fr::from(3u64);
1179        let id1 = compute_contract_class_id(artifact_hash, root, commitment);
1180        let id2 = compute_contract_class_id(artifact_hash, root, commitment);
1181        assert_eq!(id1, id2);
1182        assert_ne!(id1, Fr::zero());
1183    }
1184
1185    #[test]
1186    fn buffer_as_fields_basic() {
1187        let data = vec![0u8; 31];
1188        let fields = buffer_as_fields(&data, 100).expect("encode");
1189        // Result is padded to max_fields; first field is the length prefix,
1190        // second is the single 31-byte chunk, rest are zero-padding.
1191        assert_eq!(fields.len(), 100);
1192        assert_eq!(fields[0], Fr::from(31u64)); // length prefix
1193    }
1194
1195    #[test]
1196    fn buffer_as_fields_multiple_chunks() {
1197        let data = vec![0xffu8; 62]; // 2 chunks of 31 bytes
1198        let fields = buffer_as_fields(&data, 100).expect("encode");
1199        assert_eq!(fields.len(), 100);
1200        assert_eq!(fields[0], Fr::from(62u64)); // length prefix
1201    }
1202
1203    #[test]
1204    fn public_bytecode_commitment_empty() {
1205        let result = compute_public_bytecode_commitment(&[]);
1206        // Even with empty bytecode the Poseidon2 hash with separator is non-zero.
1207        assert_ne!(result, Fr::zero());
1208    }
1209
1210    #[test]
1211    fn public_bytecode_commitment_non_empty() {
1212        let data = vec![0x01u8; 100];
1213        let result = compute_public_bytecode_commitment(&data);
1214        assert_ne!(result, Fr::zero());
1215    }
1216
1217    #[test]
1218    fn salted_initialization_hash_uses_partial_address_separator() {
1219        let salt = Fr::from(1u64);
1220        let init_hash = Fr::from(2u64);
1221        let deployer = AztecAddress(Fr::from(3u64));
1222        let result = compute_salted_initialization_hash(salt, init_hash, deployer);
1223        let expected = poseidon2_hash_with_separator(
1224            &[salt, init_hash, deployer.0],
1225            domain_separator::PARTIAL_ADDRESS,
1226        );
1227        assert_eq!(result, expected);
1228    }
1229
1230    #[test]
1231    fn partial_address_uses_correct_separator() {
1232        let class_id = Fr::from(100u64);
1233        let salted = Fr::from(200u64);
1234        let result = compute_partial_address(class_id, salted);
1235        let expected =
1236            poseidon2_hash_with_separator(&[class_id, salted], domain_separator::PARTIAL_ADDRESS);
1237        assert_eq!(result, expected);
1238    }
1239
1240    #[test]
1241    fn contract_address_from_instance_default_keys() {
1242        use crate::types::{ContractInstance, PublicKeys};
1243        let instance = ContractInstance {
1244            version: 1,
1245            salt: Fr::from(42u64),
1246            deployer: AztecAddress(Fr::zero()),
1247            current_contract_class_id: Fr::from(100u64),
1248            original_contract_class_id: Fr::from(100u64),
1249            initialization_hash: Fr::zero(),
1250            public_keys: PublicKeys::default(),
1251        };
1252        let address =
1253            compute_contract_address_from_instance(&instance).expect("address derivation");
1254        assert_ne!(address.0, Fr::zero());
1255    }
1256
1257    #[test]
1258    fn contract_address_is_deterministic() {
1259        use crate::types::{ContractInstance, PublicKeys};
1260        let instance = ContractInstance {
1261            version: 1,
1262            salt: Fr::from(99u64),
1263            deployer: AztecAddress(Fr::from(1u64)),
1264            current_contract_class_id: Fr::from(200u64),
1265            original_contract_class_id: Fr::from(200u64),
1266            initialization_hash: Fr::from(300u64),
1267            public_keys: PublicKeys::default(),
1268        };
1269        let addr1 = compute_contract_address_from_instance(&instance).expect("addr1");
1270        let addr2 = compute_contract_address_from_instance(&instance).expect("addr2");
1271        assert_eq!(addr1, addr2);
1272    }
1273
1274    #[test]
1275    fn artifact_hash_deterministic() {
1276        let artifact = ContractArtifact {
1277            name: "Test".to_owned(),
1278            functions: vec![],
1279            outputs: None,
1280            file_map: None,
1281            context_inputs_sizes: None,
1282        };
1283        let h1 = compute_artifact_hash(&artifact);
1284        let h2 = compute_artifact_hash(&artifact);
1285        assert_eq!(h1, h2);
1286    }
1287
1288    #[test]
1289    fn class_id_from_artifact_no_functions() {
1290        let artifact = ContractArtifact {
1291            name: "Empty".to_owned(),
1292            functions: vec![],
1293            outputs: None,
1294            file_map: None,
1295            context_inputs_sizes: None,
1296        };
1297        let id = compute_contract_class_id_from_artifact(&artifact).expect("class id");
1298        assert_ne!(id, Fr::zero());
1299    }
1300
1301    #[test]
1302    fn protocol_contracts_hash_is_deterministic() {
1303        let h1 = compute_protocol_contracts_hash();
1304        let h2 = compute_protocol_contracts_hash();
1305        assert_eq!(h1, h2);
1306        assert_ne!(h1, Fr::zero());
1307        assert_eq!(
1308            h1,
1309            Fr::from_hex("0x2672340d9a0107a7b81e6d10d25b854debe613f3272e8738e8df0ca2ff297141")
1310                .expect("valid expected protocol contracts hash")
1311        );
1312    }
1313}