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 using Noir's
309            // two's-complement-in-width convention.  For negative values the
310            // circuit expects the bits wrapped to the declared width, not to
311            // u128 — otherwise this hash (computed over the flattened
312            // AbiValues in the SDK's account-entrypoint args hash) diverges
313            // from the hash the target circuit computes over its witness
314            // (which the ABI encoder correctly wraps to the declared width in
315            // `encoder.rs`).
316            //
317            // `flatten_abi_value` has no ABI type info.  We assume i64 for
318            // anything in i64 range — Noir's canonical signed integer type —
319            // and fall through to u128 two's-complement for the outer range,
320            // matching the encoder's `width >= 128` path.
321            let u_val: u128 = if *i >= 0 {
322                *i as u128
323            } else if *i >= -(1i128 << 63) {
324                (*i + (1i128 << 64)) as u128
325            } else {
326                *i as u128
327            };
328            let bytes = u_val.to_be_bytes();
329            let mut padded = [0u8; 32];
330            padded[16..].copy_from_slice(&bytes);
331            out.push(Fr::from(padded));
332        }
333        AbiValue::Array(items) => {
334            for item in items {
335                flatten_abi_value(item, out);
336            }
337        }
338        AbiValue::String(s) => {
339            for byte in s.bytes() {
340                out.push(Fr::from(u64::from(byte)));
341            }
342        }
343        AbiValue::Struct(map) => {
344            // BTreeMap iterates in key order (deterministic).
345            for value in map.values() {
346                flatten_abi_value(value, out);
347            }
348        }
349        AbiValue::Tuple(items) => {
350            for item in items {
351                flatten_abi_value(item, out);
352            }
353        }
354    }
355}
356
357/// Compute the inner authwit hash from a caller address and a function call.
358///
359/// Computes `computeInnerAuthWitHash([caller, call.selector, varArgsHash(call.args)])`.
360///
361/// Mirrors TS `computeInnerAuthWitHashFromAction(caller, action)`.
362pub fn compute_inner_auth_wit_hash_from_action(caller: &AztecAddress, call: &FunctionCall) -> Fr {
363    let args_as_fields = abi_values_to_fields(&call.args);
364    let args_hash = compute_var_args_hash(&args_as_fields);
365    compute_inner_auth_wit_hash(&[caller.0, call.selector.to_field(), args_hash])
366}
367
368/// Chain identification information.
369///
370/// This is defined in `aztec-core` so that hash functions can use it
371/// without creating a circular dependency with `aztec-wallet`.
372#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
373#[serde(rename_all = "camelCase")]
374pub struct ChainInfo {
375    /// The L2 chain ID.
376    pub chain_id: Fr,
377    /// The rollup protocol version.
378    pub version: Fr,
379}
380
381/// Either a raw message hash, a structured call intent, or a pre-computed
382/// inner hash with its consumer address.
383///
384/// Mirrors the TS distinction between `Fr`, `CallIntent`, and `IntentInnerHash`.
385#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
386#[serde(tag = "type", rename_all = "snake_case")]
387pub enum MessageHashOrIntent {
388    /// A raw message hash (already computed outer hash).
389    Hash {
390        /// The hash value.
391        hash: Fr,
392    },
393    /// A structured call intent.
394    Intent {
395        /// The caller requesting authorization.
396        caller: AztecAddress,
397        /// The function call to authorize.
398        call: FunctionCall,
399    },
400    /// A pre-computed inner hash with consumer address.
401    ///
402    /// Used when the inner hash is already known but the outer hash
403    /// (which includes chain info) still needs to be computed.
404    InnerHash {
405        /// The consumer contract address.
406        consumer: AztecAddress,
407        /// The inner hash value.
408        inner_hash: Fr,
409    },
410}
411
412/// Compute the full authwit message hash from an intent and chain info.
413///
414/// For `MessageHashOrIntent::Hash` — returns the hash directly.
415/// For `MessageHashOrIntent::Intent { caller, call }`:
416///   1. `inner_hash = compute_inner_auth_wit_hash_from_action(caller, call)`
417///   2. `consumer = call.to` (the contract being called)
418///   3. `outer_hash = compute_outer_auth_wit_hash(consumer, chain_id, version, inner_hash)`
419///
420/// For `MessageHashOrIntent::InnerHash { consumer, inner_hash }`:
421///   1. `outer_hash = compute_outer_auth_wit_hash(consumer, chain_id, version, inner_hash)`
422///
423/// Mirrors TS `computeAuthWitMessageHash(intent, metadata)`.
424pub fn compute_auth_wit_message_hash(intent: &MessageHashOrIntent, chain_info: &ChainInfo) -> Fr {
425    match intent {
426        MessageHashOrIntent::Hash { hash } => *hash,
427        MessageHashOrIntent::Intent { caller, call } => {
428            let inner_hash = compute_inner_auth_wit_hash_from_action(caller, call);
429            compute_outer_auth_wit_hash(
430                &call.to,
431                &chain_info.chain_id,
432                &chain_info.version,
433                &inner_hash,
434            )
435        }
436        MessageHashOrIntent::InnerHash {
437            consumer,
438            inner_hash,
439        } => compute_outer_auth_wit_hash(
440            consumer,
441            &chain_info.chain_id,
442            &chain_info.version,
443            inner_hash,
444        ),
445    }
446}
447
448// ---------------------------------------------------------------------------
449// Deployment hash primitives
450// ---------------------------------------------------------------------------
451
452/// Compute the initialization hash for a contract deployment.
453///
454/// Returns `Fr::zero()` if `init_fn` is `None` (no constructor).
455///
456/// Formula: `poseidon2_hash_with_separator([selector, args_hash], INITIALIZER)`
457pub fn compute_initialization_hash(
458    init_fn: Option<&FunctionArtifact>,
459    args: &[AbiValue],
460) -> Result<Fr, Error> {
461    match init_fn {
462        None => Ok(Fr::zero()),
463        Some(func) => {
464            let selector = func.selector.unwrap_or_else(|| {
465                FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
466            });
467            let encoded_args = encode_arguments(func, args)?;
468            let args_hash = compute_var_args_hash(&encoded_args);
469            Ok(poseidon2_hash_with_separator(
470                &[selector.to_field(), args_hash],
471                domain_separator::INITIALIZER,
472            ))
473        }
474    }
475}
476
477/// Compute initialization hash from pre-encoded selector and args.
478pub fn compute_initialization_hash_from_encoded(selector: Fr, encoded_args: &[Fr]) -> Fr {
479    let args_hash = compute_var_args_hash(encoded_args);
480    poseidon2_hash_with_separator(&[selector, args_hash], domain_separator::INITIALIZER)
481}
482
483// ---------------------------------------------------------------------------
484// Contract class ID computation (Step 5.5)
485// ---------------------------------------------------------------------------
486
487/// Compute the root of the private functions Merkle tree.
488///
489/// Each leaf = `poseidon2_hash_with_separator([selector, vk_hash], PRIVATE_FUNCTION_LEAF)`.
490/// Tree height = `FUNCTION_TREE_HEIGHT` (7).
491pub fn compute_private_functions_root(private_functions: &mut [(FunctionSelector, Fr)]) -> Fr {
492    let tree_height = constants::FUNCTION_TREE_HEIGHT;
493    let num_leaves = 1usize << tree_height; // 128
494
495    // Sort by selector bytes (big-endian u32 value).
496    private_functions.sort_by_key(|(sel, _)| u32::from_be_bytes(sel.0));
497
498    // Compute leaves.
499    let zero_leaf = poseidon2_hash(&[Fr::zero(), Fr::zero()]);
500    let mut leaves: Vec<Fr> = Vec::with_capacity(num_leaves);
501    for (sel, vk_hash) in private_functions.iter() {
502        let leaf = poseidon2_hash_with_separator(
503            &[sel.to_field(), *vk_hash],
504            domain_separator::PRIVATE_FUNCTION_LEAF,
505        );
506        leaves.push(leaf);
507    }
508    // Pad remaining leaves with zeros.
509    leaves.resize(num_leaves, zero_leaf);
510
511    // Build Merkle tree bottom-up using Poseidon2 for internal nodes.
512    poseidon_merkle_root(&leaves)
513}
514
515/// Build a binary Merkle tree root from leaves using Poseidon2.
516fn poseidon_merkle_root(leaves: &[Fr]) -> Fr {
517    if leaves.is_empty() {
518        return Fr::zero();
519    }
520    if leaves.len() == 1 {
521        return leaves[0];
522    }
523
524    let mut current = leaves.to_vec();
525    while current.len() > 1 {
526        let mut next = Vec::with_capacity(current.len().div_ceil(2));
527        for chunk in current.chunks(2) {
528            let left = chunk[0];
529            let right = if chunk.len() > 1 {
530                chunk[1]
531            } else {
532                Fr::zero()
533            };
534            next.push(poseidon2_hash(&[left, right]));
535        }
536        current = next;
537    }
538    current[0]
539}
540
541fn sha256_merkle_root(leaves: &[Fr]) -> Fr {
542    if leaves.is_empty() {
543        return Fr::zero();
544    }
545    if leaves.len() == 1 {
546        return leaves[0];
547    }
548
549    let mut current = leaves.to_vec();
550    while current.len() > 1 {
551        let mut next = Vec::with_capacity(current.len().div_ceil(2));
552        for chunk in current.chunks(2) {
553            let left = chunk[0].to_be_bytes();
554            let right = chunk.get(1).unwrap_or(&Fr::zero()).to_be_bytes();
555            next.push(sha256_to_field(
556                &[left.as_slice(), right.as_slice()].concat(),
557            ));
558        }
559        current = next;
560    }
561    current[0]
562}
563
564/// Compute the SHA256 hash of a byte slice, returning the result as an `Fr`.
565///
566/// Matches the TS `sha256ToField` which truncates to 31 bytes and prepends a
567/// zero byte, guaranteeing the result fits in the BN254 scalar field.
568/// Public version of `sha256_to_field` for cross-crate use.
569pub fn sha256_to_field_pub(data: &[u8]) -> Fr {
570    sha256_to_field(data)
571}
572
573fn sha256_to_field(data: &[u8]) -> Fr {
574    let hash = Sha256::digest(data);
575    let mut bytes = [0u8; 32];
576    // Take first 31 bytes of hash, prepend 0x00 — matches TS `truncateAndPad`.
577    bytes[1..].copy_from_slice(&hash[..31]);
578    Fr::from(bytes)
579}
580
581/// Compute the artifact hash for a contract.
582///
583/// Uses SHA256 for per-function bytecode/metadata hashing, then combines
584/// private and unconstrained function artifact tree roots with a metadata hash.
585pub fn compute_artifact_hash(artifact: &ContractArtifact) -> Fr {
586    let private_fn_tree_root = compute_artifact_function_tree_root(artifact, false);
587    let unconstrained_fn_tree_root = compute_artifact_function_tree_root(artifact, true);
588    let metadata_hash = compute_artifact_metadata_hash(artifact);
589
590    let mut data = Vec::new();
591    data.push(1u8);
592    data.extend_from_slice(&private_fn_tree_root.to_be_bytes());
593    data.extend_from_slice(&unconstrained_fn_tree_root.to_be_bytes());
594    data.extend_from_slice(&metadata_hash.to_be_bytes());
595    sha256_to_field(&data)
596}
597
598fn canonical_json_string(value: &serde_json::Value) -> String {
599    match value {
600        serde_json::Value::Null => "null".to_owned(),
601        serde_json::Value::Bool(boolean) => boolean.to_string(),
602        serde_json::Value::Number(number) => number.to_string(),
603        serde_json::Value::String(string) => {
604            serde_json::to_string(string).unwrap_or_else(|_| "\"\"".to_owned())
605        }
606        serde_json::Value::Array(items) => {
607            let inner = items
608                .iter()
609                .map(canonical_json_string)
610                .collect::<Vec<_>>()
611                .join(",");
612            format!("[{inner}]")
613        }
614        serde_json::Value::Object(map) => {
615            let mut entries = map.iter().collect::<Vec<_>>();
616            entries.sort_by(|(left, _), (right, _)| left.cmp(right));
617            let inner = entries
618                .into_iter()
619                .map(|(key, value)| {
620                    let key = serde_json::to_string(key).unwrap_or_else(|_| "\"\"".to_owned());
621                    format!("{key}:{}", canonical_json_string(value))
622                })
623                .collect::<Vec<_>>()
624                .join(",");
625            format!("{{{inner}}}")
626        }
627    }
628}
629
630fn decode_artifact_bytes(encoded: &str) -> Vec<u8> {
631    if let Some(hex) = encoded.strip_prefix("0x") {
632        return hex::decode(hex).unwrap_or_else(|_| encoded.as_bytes().to_vec());
633    }
634
635    use base64::Engine;
636    base64::engine::general_purpose::STANDARD
637        .decode(encoded)
638        .unwrap_or_else(|_| encoded.as_bytes().to_vec())
639}
640
641/// Compute the artifact function tree root for private or unconstrained functions.
642fn compute_artifact_function_tree_root(artifact: &ContractArtifact, unconstrained: bool) -> Fr {
643    let functions: Vec<&FunctionArtifact> = artifact
644        .functions
645        .iter()
646        .filter(|f| {
647            if unconstrained {
648                f.function_type == FunctionType::Utility || f.is_unconstrained == Some(true)
649            } else {
650                f.function_type == FunctionType::Private
651            }
652        })
653        .collect();
654
655    if functions.is_empty() {
656        return Fr::zero();
657    }
658
659    let leaves: Vec<Fr> = functions
660        .iter()
661        .map(|func| {
662            let selector = func.selector.unwrap_or_else(|| {
663                FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
664            });
665            let metadata_hash = compute_function_metadata_hash(func);
666            let bytecode_hash = compute_function_bytecode_hash(func);
667
668            let mut leaf_data = Vec::new();
669            leaf_data.push(1u8);
670            leaf_data.extend_from_slice(&selector.0);
671            leaf_data.extend_from_slice(&metadata_hash.to_be_bytes());
672            leaf_data.extend_from_slice(&bytecode_hash.to_be_bytes());
673            sha256_to_field(&leaf_data)
674        })
675        .collect();
676
677    let height = if leaves.len() <= 1 {
678        0
679    } else {
680        (leaves.len() as f64).log2().ceil() as usize
681    };
682    let num_leaves = 1usize << height;
683    let mut padded = leaves;
684    padded.resize(num_leaves.max(1), Fr::zero());
685    sha256_merkle_root(&padded)
686}
687
688/// Hash function metadata exactly as upstream does.
689fn compute_function_metadata_hash(func: &FunctionArtifact) -> Fr {
690    let metadata = serde_json::to_value(&func.return_types).unwrap_or(serde_json::Value::Null);
691    let serialized = canonical_json_string(&metadata);
692    sha256_to_field(serialized.as_bytes())
693}
694
695/// Hash function bytecode.
696fn compute_function_bytecode_hash(func: &FunctionArtifact) -> Fr {
697    match &func.bytecode {
698        Some(bc) if !bc.is_empty() => sha256_to_field(&decode_artifact_bytes(bc)),
699        _ => Fr::zero(),
700    }
701}
702
703/// Hash artifact-level metadata.
704fn compute_artifact_metadata_hash(artifact: &ContractArtifact) -> Fr {
705    let mut metadata = serde_json::Map::new();
706    metadata.insert(
707        "name".to_owned(),
708        serde_json::Value::String(artifact.name.clone()),
709    );
710    if let Some(outputs) = &artifact.outputs {
711        metadata.insert("outputs".to_owned(), outputs.clone());
712    }
713    let serialized = canonical_json_string(&serde_json::Value::Object(metadata));
714    sha256_to_field(serialized.as_bytes())
715}
716
717/// Compute the commitment to packed public bytecode.
718///
719/// Encodes bytecode as field elements (31 bytes each) and hashes with Poseidon2.
720pub fn compute_public_bytecode_commitment(packed_bytecode: &[u8]) -> Fr {
721    let fields = crate::abi::buffer_as_fields(
722        packed_bytecode,
723        constants::MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
724    )
725    .expect("packed bytecode exceeds maximum field count");
726    let byte_length = fields[0].to_usize() as u64;
727    let length_in_fields = byte_length.div_ceil(31) as usize;
728
729    let separator = Fr::from(u64::from(domain_separator::PUBLIC_BYTECODE) + (byte_length << 32));
730    poseidon2_hash_with_separator_field(&fields[1..1 + length_in_fields], separator)
731}
732
733/// Compute the contract class ID from its components.
734///
735/// `class_id = poseidon2_hash_with_separator([artifact_hash, private_functions_root, public_bytecode_commitment], CONTRACT_CLASS_ID)`
736pub fn compute_contract_class_id(
737    artifact_hash: Fr,
738    private_functions_root: Fr,
739    public_bytecode_commitment: Fr,
740) -> Fr {
741    poseidon2_hash_with_separator(
742        &[
743            artifact_hash,
744            private_functions_root,
745            public_bytecode_commitment,
746        ],
747        domain_separator::CONTRACT_CLASS_ID,
748    )
749}
750
751/// Compute contract class ID directly from a `ContractArtifact`.
752pub fn compute_contract_class_id_from_artifact(artifact: &ContractArtifact) -> Result<Fr, Error> {
753    let artifact_hash = compute_artifact_hash(artifact);
754    let private_fns_root = compute_private_functions_root_from_artifact(artifact)?;
755    let public_bytecode = extract_packed_public_bytecode(artifact);
756    let public_bytecode_commitment = compute_public_bytecode_commitment(&public_bytecode);
757    Ok(compute_contract_class_id(
758        artifact_hash,
759        private_fns_root,
760        public_bytecode_commitment,
761    ))
762}
763
764/// Extract private functions from an artifact and compute the root.
765pub fn compute_private_functions_root_from_artifact(
766    artifact: &ContractArtifact,
767) -> Result<Fr, Error> {
768    let mut private_fns: Vec<(FunctionSelector, Fr)> = artifact
769        .functions
770        .iter()
771        .filter(|f| f.function_type == FunctionType::Private)
772        .map(|f| {
773            let selector = f.selector.unwrap_or_else(|| {
774                FunctionSelector::from_name_and_parameters(&f.name, &f.parameters)
775            });
776            let vk_hash = f.verification_key_hash.unwrap_or(Fr::zero());
777            (selector, vk_hash)
778        })
779        .collect();
780
781    Ok(compute_private_functions_root(&mut private_fns))
782}
783
784/// Extract packed public bytecode from an artifact.
785fn extract_packed_public_bytecode(artifact: &ContractArtifact) -> Vec<u8> {
786    // Only public_dispatch carries the packed bytecode (mirrors TS retainBytecode filter).
787    artifact
788        .functions
789        .iter()
790        .find(|f| f.function_type == FunctionType::Public && f.name == "public_dispatch")
791        .and_then(|f| f.bytecode.as_ref())
792        .map(|bc| decode_artifact_bytes(bc))
793        .unwrap_or_default()
794}
795
796// ---------------------------------------------------------------------------
797// Contract address derivation (Step 5.6)
798// ---------------------------------------------------------------------------
799
800/// Compute the salted initialization hash.
801///
802/// `salted = poseidon2_hash_with_separator([salt, initialization_hash, deployer], PARTIAL_ADDRESS)`
803pub fn compute_salted_initialization_hash(
804    salt: Fr,
805    initialization_hash: Fr,
806    deployer: AztecAddress,
807) -> Fr {
808    poseidon2_hash_with_separator(
809        &[salt, initialization_hash, deployer.0],
810        domain_separator::PARTIAL_ADDRESS,
811    )
812}
813
814/// Compute the partial address from class ID and salted init hash.
815///
816/// `partial = poseidon2_hash_with_separator([class_id, salted_init_hash], PARTIAL_ADDRESS)`
817pub fn compute_partial_address(
818    original_contract_class_id: Fr,
819    salted_initialization_hash: Fr,
820) -> Fr {
821    poseidon2_hash_with_separator(
822        &[original_contract_class_id, salted_initialization_hash],
823        domain_separator::PARTIAL_ADDRESS,
824    )
825}
826
827/// Compute an Aztec address from public keys and a partial address.
828///
829/// Algorithm:
830///   1. `preaddress = poseidon2([public_keys_hash, partial_address], CONTRACT_ADDRESS_V1)`
831///   2. `address_point = (Fq(preaddress) * G) + ivpk_m`
832///   3. `address = address_point.x`
833pub fn compute_address(
834    public_keys: &PublicKeys,
835    partial_address: &Fr,
836) -> Result<AztecAddress, Error> {
837    let public_keys_hash = public_keys.hash();
838    let preaddress = poseidon2_hash_with_separator(
839        &[public_keys_hash, *partial_address],
840        domain_separator::CONTRACT_ADDRESS_V1,
841    );
842
843    // Convert Fr preaddress to Fq for Grumpkin scalar multiplication
844    // (matches TS: `new Fq(preaddress.toBigInt())`)
845    let preaddress_fq = Fq::from_be_bytes_mod_order(&preaddress.to_be_bytes());
846
847    let g = grumpkin::generator();
848    let preaddress_point = grumpkin::scalar_mul(&preaddress_fq, &g);
849
850    let ivpk_m = &public_keys.master_incoming_viewing_public_key;
851    // Point::is_zero() already checks !is_infinite, so no extra guard needed.
852    let address_point = if ivpk_m.is_zero() {
853        preaddress_point
854    } else {
855        grumpkin::point_add(&preaddress_point, ivpk_m)
856    };
857
858    if address_point.is_infinite {
859        return Err(Error::InvalidData(
860            "address derivation resulted in point at infinity".to_owned(),
861        ));
862    }
863
864    Ok(AztecAddress(address_point.x))
865}
866
867/// Compute the contract address from a `ContractInstance`.
868///
869/// ```text
870/// address = (poseidon2_hash_with_separator(
871///     [public_keys_hash, partial_address],
872///     CONTRACT_ADDRESS_V1
873/// ) * G + ivpk_m).x
874/// ```
875pub fn compute_contract_address_from_instance(
876    instance: &ContractInstance,
877) -> Result<AztecAddress, Error> {
878    let salted_init_hash = compute_salted_initialization_hash(
879        instance.salt,
880        instance.initialization_hash,
881        instance.deployer,
882    );
883    let partial_address =
884        compute_partial_address(instance.original_contract_class_id, salted_init_hash);
885
886    compute_address(&instance.public_keys, &partial_address)
887}
888
889#[cfg(test)]
890#[allow(clippy::expect_used, clippy::panic)]
891mod tests {
892    use super::*;
893    use crate::abi::{buffer_as_fields, FunctionSelector, FunctionType};
894
895    #[test]
896    fn var_args_hash_empty_returns_zero() {
897        assert_eq!(compute_var_args_hash(&[]), Fr::zero());
898    }
899
900    #[test]
901    fn poseidon2_hash_known_vector() {
902        // Test vector: hash of [1] should produce a known result.
903        // This validates the sponge construction matches barretenberg.
904        let result = poseidon2_hash(&[Fr::from(1u64)]);
905        let expected =
906            Fr::from_hex("0x168758332d5b3e2d13be8048c8011b454590e06c44bce7f702f09103eef5a373")
907                .expect("valid hex");
908        assert_eq!(
909            result, expected,
910            "Poseidon2 hash of [1] must match barretenberg test vector"
911        );
912    }
913
914    #[test]
915    fn poseidon2_hash_with_separator_prepends_separator() {
916        // hash_with_separator([a, b], sep) == hash([Fr(sep), a, b])
917        let a = Fr::from(10u64);
918        let b = Fr::from(20u64);
919        let sep = 42u32;
920
921        let result = poseidon2_hash_with_separator(&[a, b], sep);
922        let manual = poseidon2_hash(&[Fr::from(u64::from(sep)), a, b]);
923        assert_eq!(result, manual);
924    }
925
926    #[test]
927    fn secret_hash_uses_correct_separator() {
928        let secret = Fr::from(42u64);
929        let result = compute_secret_hash(&secret);
930        let expected = poseidon2_hash_with_separator(&[secret], domain_separator::SECRET_HASH);
931        assert_eq!(result, expected);
932        // Must be non-zero for a non-zero secret
933        assert!(!result.is_zero());
934    }
935
936    #[test]
937    fn secret_hash_is_deterministic() {
938        let secret = Fr::from(12345u64);
939        let h1 = compute_secret_hash(&secret);
940        let h2 = compute_secret_hash(&secret);
941        assert_eq!(h1, h2);
942    }
943
944    #[test]
945    fn var_args_hash_single_element() {
946        let result = compute_var_args_hash(&[Fr::from(42u64)]);
947        // Should be poseidon2_hash([FUNCTION_ARGS_SEP, 42])
948        let expected =
949            poseidon2_hash_with_separator(&[Fr::from(42u64)], domain_separator::FUNCTION_ARGS);
950        assert_eq!(result, expected);
951    }
952
953    #[test]
954    fn inner_auth_wit_hash_uses_correct_separator() {
955        let args = [Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)];
956        let result = compute_inner_auth_wit_hash(&args);
957        let expected = poseidon2_hash_with_separator(&args, domain_separator::AUTHWIT_INNER);
958        assert_eq!(result, expected);
959    }
960
961    #[test]
962    fn outer_auth_wit_hash_uses_correct_separator() {
963        let consumer = AztecAddress(Fr::from(100u64));
964        let chain_id = Fr::from(31337u64);
965        let version = Fr::from(1u64);
966        let inner_hash = Fr::from(999u64);
967
968        let result = compute_outer_auth_wit_hash(&consumer, &chain_id, &version, &inner_hash);
969        let expected = poseidon2_hash_with_separator(
970            &[consumer.0, chain_id, version, inner_hash],
971            domain_separator::AUTHWIT_OUTER,
972        );
973        assert_eq!(result, expected);
974    }
975
976    #[test]
977    fn inner_auth_wit_hash_from_action() {
978        let caller = AztecAddress(Fr::from(1u64));
979        let call = FunctionCall {
980            to: AztecAddress(Fr::from(2u64)),
981            selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid"),
982            args: vec![AbiValue::Field(Fr::from(100u64))],
983            function_type: FunctionType::Private,
984            is_static: false,
985            hide_msg_sender: false,
986        };
987
988        let result = compute_inner_auth_wit_hash_from_action(&caller, &call);
989
990        // Manual computation
991        let args_hash = compute_var_args_hash(&[Fr::from(100u64)]);
992        let selector_field = call.selector.to_field();
993        let expected = compute_inner_auth_wit_hash(&[caller.0, selector_field, args_hash]);
994        assert_eq!(result, expected);
995    }
996
997    #[test]
998    fn auth_wit_message_hash_passthrough() {
999        let hash = Fr::from(42u64);
1000        let chain_info = ChainInfo {
1001            chain_id: Fr::from(31337u64),
1002            version: Fr::from(1u64),
1003        };
1004        let result =
1005            compute_auth_wit_message_hash(&MessageHashOrIntent::Hash { hash }, &chain_info);
1006        assert_eq!(result, hash);
1007    }
1008
1009    #[test]
1010    fn auth_wit_message_hash_from_intent() {
1011        let caller = AztecAddress(Fr::from(10u64));
1012        let consumer = AztecAddress(Fr::from(20u64));
1013        let call = FunctionCall {
1014            to: consumer,
1015            selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
1016            args: vec![],
1017            function_type: FunctionType::Private,
1018            is_static: false,
1019            hide_msg_sender: false,
1020        };
1021        let chain_info = ChainInfo {
1022            chain_id: Fr::from(31337u64),
1023            version: Fr::from(1u64),
1024        };
1025
1026        let result = compute_auth_wit_message_hash(
1027            &MessageHashOrIntent::Intent {
1028                caller,
1029                call: call.clone(),
1030            },
1031            &chain_info,
1032        );
1033
1034        // Manual computation
1035        let inner = compute_inner_auth_wit_hash_from_action(&caller, &call);
1036        let expected = compute_outer_auth_wit_hash(
1037            &consumer,
1038            &chain_info.chain_id,
1039            &chain_info.version,
1040            &inner,
1041        );
1042        assert_eq!(result, expected);
1043    }
1044
1045    #[test]
1046    fn auth_wit_message_hash_from_inner_hash() {
1047        let consumer = AztecAddress(Fr::from(20u64));
1048        let inner_hash = Fr::from(999u64);
1049        let chain_info = ChainInfo {
1050            chain_id: Fr::from(31337u64),
1051            version: Fr::from(1u64),
1052        };
1053
1054        let result = compute_auth_wit_message_hash(
1055            &MessageHashOrIntent::InnerHash {
1056                consumer,
1057                inner_hash,
1058            },
1059            &chain_info,
1060        );
1061
1062        let expected = compute_outer_auth_wit_hash(
1063            &consumer,
1064            &chain_info.chain_id,
1065            &chain_info.version,
1066            &inner_hash,
1067        );
1068        assert_eq!(result, expected);
1069    }
1070
1071    #[test]
1072    fn abi_values_to_fields_basic_types() {
1073        let values = vec![
1074            AbiValue::Field(Fr::from(1u64)),
1075            AbiValue::Boolean(true),
1076            AbiValue::Boolean(false),
1077            AbiValue::Integer(42),
1078        ];
1079        let fields = abi_values_to_fields(&values);
1080        assert_eq!(fields.len(), 4);
1081        assert_eq!(fields[0], Fr::from(1u64));
1082        assert_eq!(fields[1], Fr::one());
1083        assert_eq!(fields[2], Fr::zero());
1084        assert_eq!(fields[3], Fr::from(42u64));
1085    }
1086
1087    #[test]
1088    fn abi_values_to_fields_nested() {
1089        let values = vec![AbiValue::Array(vec![
1090            AbiValue::Field(Fr::from(1u64)),
1091            AbiValue::Field(Fr::from(2u64)),
1092        ])];
1093        let fields = abi_values_to_fields(&values);
1094        assert_eq!(fields.len(), 2);
1095        assert_eq!(fields[0], Fr::from(1u64));
1096        assert_eq!(fields[1], Fr::from(2u64));
1097    }
1098
1099    #[test]
1100    fn message_hash_or_intent_serde_roundtrip() {
1101        let variants = vec![
1102            MessageHashOrIntent::Hash {
1103                hash: Fr::from(42u64),
1104            },
1105            MessageHashOrIntent::Intent {
1106                caller: AztecAddress(Fr::from(1u64)),
1107                call: FunctionCall {
1108                    to: AztecAddress(Fr::from(2u64)),
1109                    selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid"),
1110                    args: vec![],
1111                    function_type: FunctionType::Private,
1112                    is_static: false,
1113                    hide_msg_sender: false,
1114                },
1115            },
1116            MessageHashOrIntent::InnerHash {
1117                consumer: AztecAddress(Fr::from(3u64)),
1118                inner_hash: Fr::from(999u64),
1119            },
1120        ];
1121
1122        for variant in variants {
1123            let json = serde_json::to_string(&variant).expect("serialize");
1124            let decoded: MessageHashOrIntent = serde_json::from_str(&json).expect("deserialize");
1125            assert_eq!(decoded, variant);
1126        }
1127    }
1128
1129    #[test]
1130    fn chain_info_serde_roundtrip() {
1131        let info = ChainInfo {
1132            chain_id: Fr::from(31337u64),
1133            version: Fr::from(1u64),
1134        };
1135        let json = serde_json::to_string(&info).expect("serialize");
1136        let decoded: ChainInfo = serde_json::from_str(&json).expect("deserialize");
1137        assert_eq!(decoded, info);
1138    }
1139
1140    // -- Deployment hash tests --
1141
1142    #[test]
1143    fn initialization_hash_no_constructor_returns_zero() {
1144        let result = compute_initialization_hash(None, &[]).expect("no constructor");
1145        assert_eq!(result, Fr::zero());
1146    }
1147
1148    #[test]
1149    fn initialization_hash_with_constructor() {
1150        use crate::abi::AbiParameter;
1151        let func = FunctionArtifact {
1152            name: "constructor".to_owned(),
1153            function_type: FunctionType::Private,
1154            is_initializer: true,
1155            is_static: false,
1156            is_only_self: None,
1157            parameters: vec![AbiParameter {
1158                name: "admin".to_owned(),
1159                typ: crate::abi::AbiType::Field,
1160                visibility: None,
1161            }],
1162            return_types: vec![],
1163            error_types: None,
1164            selector: Some(FunctionSelector::from_hex("0xe5fb6c81").expect("valid")),
1165            bytecode: None,
1166            verification_key_hash: None,
1167            verification_key: None,
1168            custom_attributes: None,
1169            is_unconstrained: None,
1170            debug_symbols: None,
1171        };
1172        let args = vec![AbiValue::Field(Fr::from(42u64))];
1173        let result = compute_initialization_hash(Some(&func), &args).expect("init hash");
1174        assert_ne!(result, Fr::zero());
1175    }
1176
1177    #[test]
1178    fn initialization_hash_from_encoded() {
1179        let selector = Fr::from(12345u64);
1180        let args = vec![Fr::from(1u64), Fr::from(2u64)];
1181        let result = compute_initialization_hash_from_encoded(selector, &args);
1182        let args_hash = compute_var_args_hash(&args);
1183        let expected =
1184            poseidon2_hash_with_separator(&[selector, args_hash], domain_separator::INITIALIZER);
1185        assert_eq!(result, expected);
1186    }
1187
1188    #[test]
1189    fn private_functions_root_empty() {
1190        let root = compute_private_functions_root(&mut []);
1191        // Empty leaves all zero => root is the Merkle root of 128 zero leaves
1192        assert_ne!(root, Fr::zero()); // still a valid root, just all-zero tree
1193    }
1194
1195    #[test]
1196    fn contract_class_id_deterministic() {
1197        let artifact_hash = Fr::from(1u64);
1198        let root = Fr::from(2u64);
1199        let commitment = Fr::from(3u64);
1200        let id1 = compute_contract_class_id(artifact_hash, root, commitment);
1201        let id2 = compute_contract_class_id(artifact_hash, root, commitment);
1202        assert_eq!(id1, id2);
1203        assert_ne!(id1, Fr::zero());
1204    }
1205
1206    #[test]
1207    fn buffer_as_fields_basic() {
1208        let data = vec![0u8; 31];
1209        let fields = buffer_as_fields(&data, 100).expect("encode");
1210        // Result is padded to max_fields; first field is the length prefix,
1211        // second is the single 31-byte chunk, rest are zero-padding.
1212        assert_eq!(fields.len(), 100);
1213        assert_eq!(fields[0], Fr::from(31u64)); // length prefix
1214    }
1215
1216    #[test]
1217    fn buffer_as_fields_multiple_chunks() {
1218        let data = vec![0xffu8; 62]; // 2 chunks of 31 bytes
1219        let fields = buffer_as_fields(&data, 100).expect("encode");
1220        assert_eq!(fields.len(), 100);
1221        assert_eq!(fields[0], Fr::from(62u64)); // length prefix
1222    }
1223
1224    #[test]
1225    fn public_bytecode_commitment_empty() {
1226        let result = compute_public_bytecode_commitment(&[]);
1227        // Even with empty bytecode the Poseidon2 hash with separator is non-zero.
1228        assert_ne!(result, Fr::zero());
1229    }
1230
1231    #[test]
1232    fn public_bytecode_commitment_non_empty() {
1233        let data = vec![0x01u8; 100];
1234        let result = compute_public_bytecode_commitment(&data);
1235        assert_ne!(result, Fr::zero());
1236    }
1237
1238    #[test]
1239    fn salted_initialization_hash_uses_partial_address_separator() {
1240        let salt = Fr::from(1u64);
1241        let init_hash = Fr::from(2u64);
1242        let deployer = AztecAddress(Fr::from(3u64));
1243        let result = compute_salted_initialization_hash(salt, init_hash, deployer);
1244        let expected = poseidon2_hash_with_separator(
1245            &[salt, init_hash, deployer.0],
1246            domain_separator::PARTIAL_ADDRESS,
1247        );
1248        assert_eq!(result, expected);
1249    }
1250
1251    #[test]
1252    fn partial_address_uses_correct_separator() {
1253        let class_id = Fr::from(100u64);
1254        let salted = Fr::from(200u64);
1255        let result = compute_partial_address(class_id, salted);
1256        let expected =
1257            poseidon2_hash_with_separator(&[class_id, salted], domain_separator::PARTIAL_ADDRESS);
1258        assert_eq!(result, expected);
1259    }
1260
1261    #[test]
1262    fn contract_address_from_instance_default_keys() {
1263        use crate::types::{ContractInstance, PublicKeys};
1264        let instance = ContractInstance {
1265            version: 1,
1266            salt: Fr::from(42u64),
1267            deployer: AztecAddress(Fr::zero()),
1268            current_contract_class_id: Fr::from(100u64),
1269            original_contract_class_id: Fr::from(100u64),
1270            initialization_hash: Fr::zero(),
1271            public_keys: PublicKeys::default(),
1272        };
1273        let address =
1274            compute_contract_address_from_instance(&instance).expect("address derivation");
1275        assert_ne!(address.0, Fr::zero());
1276    }
1277
1278    #[test]
1279    fn contract_address_is_deterministic() {
1280        use crate::types::{ContractInstance, PublicKeys};
1281        let instance = ContractInstance {
1282            version: 1,
1283            salt: Fr::from(99u64),
1284            deployer: AztecAddress(Fr::from(1u64)),
1285            current_contract_class_id: Fr::from(200u64),
1286            original_contract_class_id: Fr::from(200u64),
1287            initialization_hash: Fr::from(300u64),
1288            public_keys: PublicKeys::default(),
1289        };
1290        let addr1 = compute_contract_address_from_instance(&instance).expect("addr1");
1291        let addr2 = compute_contract_address_from_instance(&instance).expect("addr2");
1292        assert_eq!(addr1, addr2);
1293    }
1294
1295    #[test]
1296    fn artifact_hash_deterministic() {
1297        let artifact = ContractArtifact {
1298            name: "Test".to_owned(),
1299            functions: vec![],
1300            outputs: None,
1301            file_map: None,
1302            context_inputs_sizes: None,
1303        };
1304        let h1 = compute_artifact_hash(&artifact);
1305        let h2 = compute_artifact_hash(&artifact);
1306        assert_eq!(h1, h2);
1307    }
1308
1309    #[test]
1310    fn class_id_from_artifact_no_functions() {
1311        let artifact = ContractArtifact {
1312            name: "Empty".to_owned(),
1313            functions: vec![],
1314            outputs: None,
1315            file_map: None,
1316            context_inputs_sizes: None,
1317        };
1318        let id = compute_contract_class_id_from_artifact(&artifact).expect("class id");
1319        assert_ne!(id, Fr::zero());
1320    }
1321
1322    #[test]
1323    fn protocol_contracts_hash_is_deterministic() {
1324        let h1 = compute_protocol_contracts_hash();
1325        let h2 = compute_protocol_contracts_hash();
1326        assert_eq!(h1, h2);
1327        assert_ne!(h1, Fr::zero());
1328        assert_eq!(
1329            h1,
1330            Fr::from_hex("0x2672340d9a0107a7b81e6d10d25b854debe613f3272e8738e8df0ca2ff297141")
1331                .expect("valid expected protocol contracts hash")
1332        );
1333    }
1334}