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, Fr};
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(crate) 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 function arguments using Poseidon2 with the `FUNCTION_ARGS` separator.
98///
99/// Returns `Fr::zero()` if `args` is empty.
100///
101/// Mirrors TS `computeVarArgsHash(args)`.
102pub fn compute_var_args_hash(args: &[Fr]) -> Fr {
103    if args.is_empty() {
104        return Fr::zero();
105    }
106    poseidon2_hash_with_separator(args, domain_separator::FUNCTION_ARGS)
107}
108
109/// Compute the inner authwit hash — the "intent" before siloing with consumer.
110///
111/// `args` is typically `[caller, selector, args_hash]`.
112/// Uses Poseidon2 with `AUTHWIT_INNER` domain separator.
113///
114/// Mirrors TS `computeInnerAuthWitHash(args)`.
115pub fn compute_inner_auth_wit_hash(args: &[Fr]) -> Fr {
116    poseidon2_hash_with_separator(args, domain_separator::AUTHWIT_INNER)
117}
118
119/// Compute the outer authwit hash — the value the approver signs.
120///
121/// Combines consumer address, chain ID, protocol version, and inner hash.
122/// Uses Poseidon2 with `AUTHWIT_OUTER` domain separator.
123///
124/// Mirrors TS `computeOuterAuthWitHash(consumer, chainId, version, innerHash)`.
125pub fn compute_outer_auth_wit_hash(
126    consumer: &AztecAddress,
127    chain_id: &Fr,
128    version: &Fr,
129    inner_hash: &Fr,
130) -> Fr {
131    poseidon2_hash_with_separator(
132        &[consumer.0, *chain_id, *version, *inner_hash],
133        domain_separator::AUTHWIT_OUTER,
134    )
135}
136
137/// Flatten ABI values into their field element representation.
138///
139/// Handles the common types used in authwit scenarios: `Field`, `Boolean`,
140/// `Integer`, `Array`, `Struct`, and `Tuple`. Strings are encoded as
141/// one field element per byte.
142pub fn abi_values_to_fields(args: &[AbiValue]) -> Vec<Fr> {
143    let mut fields = Vec::new();
144    for arg in args {
145        flatten_abi_value(arg, &mut fields);
146    }
147    fields
148}
149
150fn flatten_abi_value(value: &AbiValue, out: &mut Vec<Fr>) {
151    match value {
152        AbiValue::Field(f) => out.push(*f),
153        AbiValue::Boolean(b) => out.push(if *b { Fr::one() } else { Fr::zero() }),
154        AbiValue::Integer(i) => {
155            // Integers are encoded as unsigned field elements.
156            // Negative values are not expected in authwit args.
157            out.push(Fr::from(*i as u64));
158        }
159        AbiValue::Array(items) => {
160            for item in items {
161                flatten_abi_value(item, out);
162            }
163        }
164        AbiValue::String(s) => {
165            for byte in s.bytes() {
166                out.push(Fr::from(u64::from(byte)));
167            }
168        }
169        AbiValue::Struct(map) => {
170            // BTreeMap iterates in key order (deterministic).
171            for value in map.values() {
172                flatten_abi_value(value, out);
173            }
174        }
175        AbiValue::Tuple(items) => {
176            for item in items {
177                flatten_abi_value(item, out);
178            }
179        }
180    }
181}
182
183/// Compute the inner authwit hash from a caller address and a function call.
184///
185/// Computes `computeInnerAuthWitHash([caller, call.selector, varArgsHash(call.args)])`.
186///
187/// Mirrors TS `computeInnerAuthWitHashFromAction(caller, action)`.
188pub fn compute_inner_auth_wit_hash_from_action(caller: &AztecAddress, call: &FunctionCall) -> Fr {
189    let args_as_fields = abi_values_to_fields(&call.args);
190    let args_hash = compute_var_args_hash(&args_as_fields);
191    compute_inner_auth_wit_hash(&[caller.0, call.selector.to_field(), args_hash])
192}
193
194/// Chain identification information.
195///
196/// This is defined in `aztec-core` so that hash functions can use it
197/// without creating a circular dependency with `aztec-wallet`.
198#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct ChainInfo {
201    /// The L2 chain ID.
202    pub chain_id: Fr,
203    /// The rollup protocol version.
204    pub version: Fr,
205}
206
207/// Either a raw message hash, a structured call intent, or a pre-computed
208/// inner hash with its consumer address.
209///
210/// Mirrors the TS distinction between `Fr`, `CallIntent`, and `IntentInnerHash`.
211#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
212#[serde(tag = "type", rename_all = "snake_case")]
213pub enum MessageHashOrIntent {
214    /// A raw message hash (already computed outer hash).
215    Hash {
216        /// The hash value.
217        hash: Fr,
218    },
219    /// A structured call intent.
220    Intent {
221        /// The caller requesting authorization.
222        caller: AztecAddress,
223        /// The function call to authorize.
224        call: FunctionCall,
225    },
226    /// A pre-computed inner hash with consumer address.
227    ///
228    /// Used when the inner hash is already known but the outer hash
229    /// (which includes chain info) still needs to be computed.
230    InnerHash {
231        /// The consumer contract address.
232        consumer: AztecAddress,
233        /// The inner hash value.
234        inner_hash: Fr,
235    },
236}
237
238/// Compute the full authwit message hash from an intent and chain info.
239///
240/// For `MessageHashOrIntent::Hash` — returns the hash directly.
241/// For `MessageHashOrIntent::Intent { caller, call }`:
242///   1. `inner_hash = compute_inner_auth_wit_hash_from_action(caller, call)`
243///   2. `consumer = call.to` (the contract being called)
244///   3. `outer_hash = compute_outer_auth_wit_hash(consumer, chain_id, version, inner_hash)`
245///
246/// For `MessageHashOrIntent::InnerHash { consumer, inner_hash }`:
247///   1. `outer_hash = compute_outer_auth_wit_hash(consumer, chain_id, version, inner_hash)`
248///
249/// Mirrors TS `computeAuthWitMessageHash(intent, metadata)`.
250pub fn compute_auth_wit_message_hash(intent: &MessageHashOrIntent, chain_info: &ChainInfo) -> Fr {
251    match intent {
252        MessageHashOrIntent::Hash { hash } => *hash,
253        MessageHashOrIntent::Intent { caller, call } => {
254            let inner_hash = compute_inner_auth_wit_hash_from_action(caller, call);
255            compute_outer_auth_wit_hash(
256                &call.to,
257                &chain_info.chain_id,
258                &chain_info.version,
259                &inner_hash,
260            )
261        }
262        MessageHashOrIntent::InnerHash {
263            consumer,
264            inner_hash,
265        } => compute_outer_auth_wit_hash(
266            consumer,
267            &chain_info.chain_id,
268            &chain_info.version,
269            inner_hash,
270        ),
271    }
272}
273
274// ---------------------------------------------------------------------------
275// Deployment hash primitives
276// ---------------------------------------------------------------------------
277
278/// Compute the initialization hash for a contract deployment.
279///
280/// Returns `Fr::zero()` if `init_fn` is `None` (no constructor).
281///
282/// Formula: `poseidon2_hash_with_separator([selector, args_hash], INITIALIZER)`
283pub fn compute_initialization_hash(
284    init_fn: Option<&FunctionArtifact>,
285    args: &[AbiValue],
286) -> Result<Fr, Error> {
287    match init_fn {
288        None => Ok(Fr::zero()),
289        Some(func) => {
290            let selector = func.selector.unwrap_or_else(|| {
291                FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
292            });
293            let encoded_args = encode_arguments(func, args)?;
294            let args_hash = compute_var_args_hash(&encoded_args);
295            Ok(poseidon2_hash_with_separator(
296                &[selector.to_field(), args_hash],
297                domain_separator::INITIALIZER,
298            ))
299        }
300    }
301}
302
303/// Compute initialization hash from pre-encoded selector and args.
304pub fn compute_initialization_hash_from_encoded(selector: Fr, encoded_args: &[Fr]) -> Fr {
305    let args_hash = compute_var_args_hash(encoded_args);
306    poseidon2_hash_with_separator(&[selector, args_hash], domain_separator::INITIALIZER)
307}
308
309// ---------------------------------------------------------------------------
310// Contract class ID computation (Step 5.5)
311// ---------------------------------------------------------------------------
312
313/// Compute the root of the private functions Merkle tree.
314///
315/// Each leaf = `poseidon2_hash_with_separator([selector, vk_hash], PRIVATE_FUNCTION_LEAF)`.
316/// Tree height = `FUNCTION_TREE_HEIGHT` (7).
317pub fn compute_private_functions_root(private_functions: &mut [(FunctionSelector, Fr)]) -> Fr {
318    let tree_height = constants::FUNCTION_TREE_HEIGHT;
319    let num_leaves = 1usize << tree_height; // 128
320
321    // Sort by selector bytes (big-endian u32 value).
322    private_functions.sort_by_key(|(sel, _)| u32::from_be_bytes(sel.0));
323
324    // Compute leaves.
325    let zero_leaf = poseidon2_hash(&[Fr::zero(), Fr::zero()]);
326    let mut leaves: Vec<Fr> = Vec::with_capacity(num_leaves);
327    for (sel, vk_hash) in private_functions.iter() {
328        let leaf = poseidon2_hash_with_separator(
329            &[sel.to_field(), *vk_hash],
330            domain_separator::PRIVATE_FUNCTION_LEAF,
331        );
332        leaves.push(leaf);
333    }
334    // Pad remaining leaves with zeros.
335    leaves.resize(num_leaves, zero_leaf);
336
337    // Build Merkle tree bottom-up using Poseidon2 for internal nodes.
338    poseidon_merkle_root(&leaves)
339}
340
341/// Build a binary Merkle tree root from leaves using Poseidon2.
342fn poseidon_merkle_root(leaves: &[Fr]) -> Fr {
343    if leaves.is_empty() {
344        return Fr::zero();
345    }
346    if leaves.len() == 1 {
347        return leaves[0];
348    }
349
350    let mut current = leaves.to_vec();
351    while current.len() > 1 {
352        let mut next = Vec::with_capacity(current.len().div_ceil(2));
353        for chunk in current.chunks(2) {
354            let left = chunk[0];
355            let right = if chunk.len() > 1 {
356                chunk[1]
357            } else {
358                Fr::zero()
359            };
360            next.push(poseidon2_hash(&[left, right]));
361        }
362        current = next;
363    }
364    current[0]
365}
366
367fn sha256_merkle_root(leaves: &[Fr]) -> Fr {
368    if leaves.is_empty() {
369        return Fr::zero();
370    }
371    if leaves.len() == 1 {
372        return leaves[0];
373    }
374
375    let mut current = leaves.to_vec();
376    while current.len() > 1 {
377        let mut next = Vec::with_capacity(current.len().div_ceil(2));
378        for chunk in current.chunks(2) {
379            let left = chunk[0].to_be_bytes();
380            let right = chunk.get(1).unwrap_or(&Fr::zero()).to_be_bytes();
381            next.push(sha256_to_field(
382                &[left.as_slice(), right.as_slice()].concat(),
383            ));
384        }
385        current = next;
386    }
387    current[0]
388}
389
390/// Compute the SHA256 hash of a byte slice, returning the result as an `Fr`
391/// (reduced mod the BN254 scalar field order).
392fn sha256_to_field(data: &[u8]) -> Fr {
393    let hash = Sha256::digest(data);
394    Fr::from(<[u8; 32]>::try_from(hash.as_slice()).expect("SHA256 is 32 bytes"))
395}
396
397/// Compute the artifact hash for a contract.
398///
399/// Uses SHA256 for per-function bytecode/metadata hashing, then combines
400/// private and unconstrained function artifact tree roots with a metadata hash.
401pub fn compute_artifact_hash(artifact: &ContractArtifact) -> Fr {
402    let private_fn_tree_root = compute_artifact_function_tree_root(artifact, false);
403    let unconstrained_fn_tree_root = compute_artifact_function_tree_root(artifact, true);
404    let metadata_hash = compute_artifact_metadata_hash(artifact);
405
406    let mut data = Vec::new();
407    data.push(1u8);
408    data.extend_from_slice(&private_fn_tree_root.to_be_bytes());
409    data.extend_from_slice(&unconstrained_fn_tree_root.to_be_bytes());
410    data.extend_from_slice(&metadata_hash.to_be_bytes());
411    sha256_to_field(&data)
412}
413
414fn canonical_json_string(value: &serde_json::Value) -> String {
415    match value {
416        serde_json::Value::Null => "null".to_owned(),
417        serde_json::Value::Bool(boolean) => boolean.to_string(),
418        serde_json::Value::Number(number) => number.to_string(),
419        serde_json::Value::String(string) => {
420            serde_json::to_string(string).unwrap_or_else(|_| "\"\"".to_owned())
421        }
422        serde_json::Value::Array(items) => {
423            let inner = items
424                .iter()
425                .map(canonical_json_string)
426                .collect::<Vec<_>>()
427                .join(",");
428            format!("[{inner}]")
429        }
430        serde_json::Value::Object(map) => {
431            let mut entries = map.iter().collect::<Vec<_>>();
432            entries.sort_by(|(left, _), (right, _)| left.cmp(right));
433            let inner = entries
434                .into_iter()
435                .map(|(key, value)| {
436                    let key = serde_json::to_string(key).unwrap_or_else(|_| "\"\"".to_owned());
437                    format!("{key}:{}", canonical_json_string(value))
438                })
439                .collect::<Vec<_>>()
440                .join(",");
441            format!("{{{inner}}}")
442        }
443    }
444}
445
446fn decode_artifact_bytes(encoded: &str) -> Vec<u8> {
447    if let Some(hex) = encoded.strip_prefix("0x") {
448        return hex::decode(hex).unwrap_or_else(|_| encoded.as_bytes().to_vec());
449    }
450
451    use base64::Engine;
452    base64::engine::general_purpose::STANDARD
453        .decode(encoded)
454        .unwrap_or_else(|_| encoded.as_bytes().to_vec())
455}
456
457/// Compute the artifact function tree root for private or unconstrained functions.
458fn compute_artifact_function_tree_root(artifact: &ContractArtifact, unconstrained: bool) -> Fr {
459    let functions: Vec<&FunctionArtifact> = artifact
460        .functions
461        .iter()
462        .filter(|f| {
463            if unconstrained {
464                f.function_type == FunctionType::Utility || f.is_unconstrained == Some(true)
465            } else {
466                f.function_type == FunctionType::Private
467            }
468        })
469        .collect();
470
471    if functions.is_empty() {
472        return Fr::zero();
473    }
474
475    let leaves: Vec<Fr> = functions
476        .iter()
477        .map(|func| {
478            let selector = func.selector.unwrap_or_else(|| {
479                FunctionSelector::from_name_and_parameters(&func.name, &func.parameters)
480            });
481            let metadata_hash = compute_function_metadata_hash(func);
482            let bytecode_hash = compute_function_bytecode_hash(func);
483
484            let mut leaf_data = Vec::new();
485            leaf_data.push(1u8);
486            leaf_data.extend_from_slice(&selector.0);
487            leaf_data.extend_from_slice(&metadata_hash.to_be_bytes());
488            leaf_data.extend_from_slice(&bytecode_hash.to_be_bytes());
489            sha256_to_field(&leaf_data)
490        })
491        .collect();
492
493    let height = if leaves.len() <= 1 {
494        0
495    } else {
496        (leaves.len() as f64).log2().ceil() as usize
497    };
498    let num_leaves = 1usize << height;
499    let mut padded = leaves;
500    padded.resize(num_leaves.max(1), Fr::zero());
501    sha256_merkle_root(&padded)
502}
503
504/// Hash function metadata exactly as upstream does.
505fn compute_function_metadata_hash(func: &FunctionArtifact) -> Fr {
506    let metadata = serde_json::to_value(&func.return_types).unwrap_or(serde_json::Value::Null);
507    let serialized = canonical_json_string(&metadata);
508    sha256_to_field(serialized.as_bytes())
509}
510
511/// Hash function bytecode.
512fn compute_function_bytecode_hash(func: &FunctionArtifact) -> Fr {
513    match &func.bytecode {
514        Some(bc) if !bc.is_empty() => sha256_to_field(&decode_artifact_bytes(bc)),
515        _ => Fr::zero(),
516    }
517}
518
519/// Hash artifact-level metadata.
520fn compute_artifact_metadata_hash(artifact: &ContractArtifact) -> Fr {
521    let mut metadata = serde_json::Map::new();
522    metadata.insert(
523        "name".to_owned(),
524        serde_json::Value::String(artifact.name.clone()),
525    );
526    if let Some(outputs) = &artifact.outputs {
527        metadata.insert("outputs".to_owned(), outputs.clone());
528    }
529    let serialized = canonical_json_string(&serde_json::Value::Object(metadata));
530    sha256_to_field(serialized.as_bytes())
531}
532
533/// Compute the commitment to packed public bytecode.
534///
535/// Encodes bytecode as field elements (31 bytes each) and hashes with Poseidon2.
536pub fn compute_public_bytecode_commitment(packed_bytecode: &[u8]) -> Fr {
537    let fields = crate::abi::buffer_as_fields(
538        packed_bytecode,
539        constants::MAX_PACKED_PUBLIC_BYTECODE_SIZE_IN_FIELDS,
540    )
541    .expect("packed bytecode exceeds maximum field count");
542    let byte_length = fields[0].to_usize() as u64;
543    let length_in_fields = byte_length.div_ceil(31) as usize;
544
545    let separator = Fr::from(u64::from(domain_separator::PUBLIC_BYTECODE) + (byte_length << 32));
546    poseidon2_hash_with_separator_field(&fields[1..1 + length_in_fields], separator)
547}
548
549/// Compute the contract class ID from its components.
550///
551/// `class_id = poseidon2_hash_with_separator([artifact_hash, private_functions_root, public_bytecode_commitment], CONTRACT_CLASS_ID)`
552pub fn compute_contract_class_id(
553    artifact_hash: Fr,
554    private_functions_root: Fr,
555    public_bytecode_commitment: Fr,
556) -> Fr {
557    poseidon2_hash_with_separator(
558        &[
559            artifact_hash,
560            private_functions_root,
561            public_bytecode_commitment,
562        ],
563        domain_separator::CONTRACT_CLASS_ID,
564    )
565}
566
567/// Compute contract class ID directly from a `ContractArtifact`.
568pub fn compute_contract_class_id_from_artifact(artifact: &ContractArtifact) -> Result<Fr, Error> {
569    let artifact_hash = compute_artifact_hash(artifact);
570    let private_fns_root = compute_private_functions_root_from_artifact(artifact)?;
571    let public_bytecode = extract_packed_public_bytecode(artifact);
572    let public_bytecode_commitment = compute_public_bytecode_commitment(&public_bytecode);
573    Ok(compute_contract_class_id(
574        artifact_hash,
575        private_fns_root,
576        public_bytecode_commitment,
577    ))
578}
579
580/// Extract private functions from an artifact and compute the root.
581pub fn compute_private_functions_root_from_artifact(
582    artifact: &ContractArtifact,
583) -> Result<Fr, Error> {
584    let mut private_fns: Vec<(FunctionSelector, Fr)> = artifact
585        .functions
586        .iter()
587        .filter(|f| f.function_type == FunctionType::Private)
588        .map(|f| {
589            let selector = f.selector.unwrap_or_else(|| {
590                FunctionSelector::from_name_and_parameters(&f.name, &f.parameters)
591            });
592            let vk_hash = f.verification_key_hash.unwrap_or(Fr::zero());
593            (selector, vk_hash)
594        })
595        .collect();
596
597    Ok(compute_private_functions_root(&mut private_fns))
598}
599
600/// Extract packed public bytecode from an artifact.
601fn extract_packed_public_bytecode(artifact: &ContractArtifact) -> Vec<u8> {
602    let mut bytecode = Vec::new();
603    for func in &artifact.functions {
604        if func.function_type == FunctionType::Public {
605            if let Some(ref bc) = func.bytecode {
606                bytecode.extend_from_slice(&decode_artifact_bytes(bc));
607            }
608        }
609    }
610    bytecode
611}
612
613// ---------------------------------------------------------------------------
614// Contract address derivation (Step 5.6)
615// ---------------------------------------------------------------------------
616
617/// Compute the salted initialization hash.
618///
619/// `salted = poseidon2_hash_with_separator([salt, initialization_hash, deployer], PARTIAL_ADDRESS)`
620pub fn compute_salted_initialization_hash(
621    salt: Fr,
622    initialization_hash: Fr,
623    deployer: AztecAddress,
624) -> Fr {
625    poseidon2_hash_with_separator(
626        &[salt, initialization_hash, deployer.0],
627        domain_separator::PARTIAL_ADDRESS,
628    )
629}
630
631/// Compute the partial address from class ID and salted init hash.
632///
633/// `partial = poseidon2_hash_with_separator([class_id, salted_init_hash], PARTIAL_ADDRESS)`
634pub fn compute_partial_address(
635    original_contract_class_id: Fr,
636    salted_initialization_hash: Fr,
637) -> Fr {
638    poseidon2_hash_with_separator(
639        &[original_contract_class_id, salted_initialization_hash],
640        domain_separator::PARTIAL_ADDRESS,
641    )
642}
643
644/// Compute the contract address from a `ContractInstance`.
645///
646/// ```text
647/// address = (poseidon2_hash_with_separator(
648///     [public_keys_hash, partial_address],
649///     CONTRACT_ADDRESS_V1
650/// ) * G + ivpk_m).x
651/// ```
652pub fn compute_contract_address_from_instance(
653    instance: &ContractInstance,
654) -> Result<AztecAddress, Error> {
655    let public_keys_hash = instance.public_keys.hash();
656
657    let salted_init_hash = compute_salted_initialization_hash(
658        instance.salt,
659        instance.initialization_hash,
660        instance.deployer,
661    );
662
663    let partial_address =
664        compute_partial_address(instance.original_contract_class_id, salted_init_hash);
665
666    let preaddress = poseidon2_hash_with_separator(
667        &[public_keys_hash, partial_address],
668        domain_separator::CONTRACT_ADDRESS_V1,
669    );
670
671    // address = (preaddress * G + ivpk_m).x
672    let g = grumpkin::generator();
673    let preaddress_point = grumpkin::scalar_mul(&preaddress, &g);
674    let ivpk_m = &instance.public_keys.master_incoming_viewing_public_key;
675
676    let address_point = if ivpk_m.is_zero() && !ivpk_m.is_infinite {
677        preaddress_point
678    } else {
679        grumpkin::point_add(&preaddress_point, ivpk_m)
680    };
681
682    if address_point.is_infinite {
683        return Err(Error::InvalidData(
684            "contract address derivation resulted in point at infinity".to_owned(),
685        ));
686    }
687
688    Ok(AztecAddress(address_point.x))
689}
690
691#[cfg(test)]
692#[allow(clippy::expect_used, clippy::panic)]
693mod tests {
694    use super::*;
695    use crate::abi::{buffer_as_fields, FunctionSelector, FunctionType};
696
697    #[test]
698    fn var_args_hash_empty_returns_zero() {
699        assert_eq!(compute_var_args_hash(&[]), Fr::zero());
700    }
701
702    #[test]
703    fn poseidon2_hash_known_vector() {
704        // Test vector: hash of [1] should produce a known result.
705        // This validates the sponge construction matches barretenberg.
706        let result = poseidon2_hash(&[Fr::from(1u64)]);
707        let expected =
708            Fr::from_hex("0x168758332d5b3e2d13be8048c8011b454590e06c44bce7f702f09103eef5a373")
709                .expect("valid hex");
710        assert_eq!(
711            result, expected,
712            "Poseidon2 hash of [1] must match barretenberg test vector"
713        );
714    }
715
716    #[test]
717    fn poseidon2_hash_with_separator_prepends_separator() {
718        // hash_with_separator([a, b], sep) == hash([Fr(sep), a, b])
719        let a = Fr::from(10u64);
720        let b = Fr::from(20u64);
721        let sep = 42u32;
722
723        let result = poseidon2_hash_with_separator(&[a, b], sep);
724        let manual = poseidon2_hash(&[Fr::from(u64::from(sep)), a, b]);
725        assert_eq!(result, manual);
726    }
727
728    #[test]
729    fn var_args_hash_single_element() {
730        let result = compute_var_args_hash(&[Fr::from(42u64)]);
731        // Should be poseidon2_hash([FUNCTION_ARGS_SEP, 42])
732        let expected =
733            poseidon2_hash_with_separator(&[Fr::from(42u64)], domain_separator::FUNCTION_ARGS);
734        assert_eq!(result, expected);
735    }
736
737    #[test]
738    fn inner_auth_wit_hash_uses_correct_separator() {
739        let args = [Fr::from(1u64), Fr::from(2u64), Fr::from(3u64)];
740        let result = compute_inner_auth_wit_hash(&args);
741        let expected = poseidon2_hash_with_separator(&args, domain_separator::AUTHWIT_INNER);
742        assert_eq!(result, expected);
743    }
744
745    #[test]
746    fn outer_auth_wit_hash_uses_correct_separator() {
747        let consumer = AztecAddress(Fr::from(100u64));
748        let chain_id = Fr::from(31337u64);
749        let version = Fr::from(1u64);
750        let inner_hash = Fr::from(999u64);
751
752        let result = compute_outer_auth_wit_hash(&consumer, &chain_id, &version, &inner_hash);
753        let expected = poseidon2_hash_with_separator(
754            &[consumer.0, chain_id, version, inner_hash],
755            domain_separator::AUTHWIT_OUTER,
756        );
757        assert_eq!(result, expected);
758    }
759
760    #[test]
761    fn inner_auth_wit_hash_from_action() {
762        let caller = AztecAddress(Fr::from(1u64));
763        let call = FunctionCall {
764            to: AztecAddress(Fr::from(2u64)),
765            selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid"),
766            args: vec![AbiValue::Field(Fr::from(100u64))],
767            function_type: FunctionType::Private,
768            is_static: false,
769        };
770
771        let result = compute_inner_auth_wit_hash_from_action(&caller, &call);
772
773        // Manual computation
774        let args_hash = compute_var_args_hash(&[Fr::from(100u64)]);
775        let selector_field = call.selector.to_field();
776        let expected = compute_inner_auth_wit_hash(&[caller.0, selector_field, args_hash]);
777        assert_eq!(result, expected);
778    }
779
780    #[test]
781    fn auth_wit_message_hash_passthrough() {
782        let hash = Fr::from(42u64);
783        let chain_info = ChainInfo {
784            chain_id: Fr::from(31337u64),
785            version: Fr::from(1u64),
786        };
787        let result =
788            compute_auth_wit_message_hash(&MessageHashOrIntent::Hash { hash }, &chain_info);
789        assert_eq!(result, hash);
790    }
791
792    #[test]
793    fn auth_wit_message_hash_from_intent() {
794        let caller = AztecAddress(Fr::from(10u64));
795        let consumer = AztecAddress(Fr::from(20u64));
796        let call = FunctionCall {
797            to: consumer,
798            selector: FunctionSelector::from_hex("0x11223344").expect("valid"),
799            args: vec![],
800            function_type: FunctionType::Private,
801            is_static: false,
802        };
803        let chain_info = ChainInfo {
804            chain_id: Fr::from(31337u64),
805            version: Fr::from(1u64),
806        };
807
808        let result = compute_auth_wit_message_hash(
809            &MessageHashOrIntent::Intent {
810                caller,
811                call: call.clone(),
812            },
813            &chain_info,
814        );
815
816        // Manual computation
817        let inner = compute_inner_auth_wit_hash_from_action(&caller, &call);
818        let expected = compute_outer_auth_wit_hash(
819            &consumer,
820            &chain_info.chain_id,
821            &chain_info.version,
822            &inner,
823        );
824        assert_eq!(result, expected);
825    }
826
827    #[test]
828    fn auth_wit_message_hash_from_inner_hash() {
829        let consumer = AztecAddress(Fr::from(20u64));
830        let inner_hash = Fr::from(999u64);
831        let chain_info = ChainInfo {
832            chain_id: Fr::from(31337u64),
833            version: Fr::from(1u64),
834        };
835
836        let result = compute_auth_wit_message_hash(
837            &MessageHashOrIntent::InnerHash {
838                consumer,
839                inner_hash,
840            },
841            &chain_info,
842        );
843
844        let expected = compute_outer_auth_wit_hash(
845            &consumer,
846            &chain_info.chain_id,
847            &chain_info.version,
848            &inner_hash,
849        );
850        assert_eq!(result, expected);
851    }
852
853    #[test]
854    fn abi_values_to_fields_basic_types() {
855        let values = vec![
856            AbiValue::Field(Fr::from(1u64)),
857            AbiValue::Boolean(true),
858            AbiValue::Boolean(false),
859            AbiValue::Integer(42),
860        ];
861        let fields = abi_values_to_fields(&values);
862        assert_eq!(fields.len(), 4);
863        assert_eq!(fields[0], Fr::from(1u64));
864        assert_eq!(fields[1], Fr::one());
865        assert_eq!(fields[2], Fr::zero());
866        assert_eq!(fields[3], Fr::from(42u64));
867    }
868
869    #[test]
870    fn abi_values_to_fields_nested() {
871        let values = vec![AbiValue::Array(vec![
872            AbiValue::Field(Fr::from(1u64)),
873            AbiValue::Field(Fr::from(2u64)),
874        ])];
875        let fields = abi_values_to_fields(&values);
876        assert_eq!(fields.len(), 2);
877        assert_eq!(fields[0], Fr::from(1u64));
878        assert_eq!(fields[1], Fr::from(2u64));
879    }
880
881    #[test]
882    fn message_hash_or_intent_serde_roundtrip() {
883        let variants = vec![
884            MessageHashOrIntent::Hash {
885                hash: Fr::from(42u64),
886            },
887            MessageHashOrIntent::Intent {
888                caller: AztecAddress(Fr::from(1u64)),
889                call: FunctionCall {
890                    to: AztecAddress(Fr::from(2u64)),
891                    selector: FunctionSelector::from_hex("0xaabbccdd").expect("valid"),
892                    args: vec![],
893                    function_type: FunctionType::Private,
894                    is_static: false,
895                },
896            },
897            MessageHashOrIntent::InnerHash {
898                consumer: AztecAddress(Fr::from(3u64)),
899                inner_hash: Fr::from(999u64),
900            },
901        ];
902
903        for variant in variants {
904            let json = serde_json::to_string(&variant).expect("serialize");
905            let decoded: MessageHashOrIntent = serde_json::from_str(&json).expect("deserialize");
906            assert_eq!(decoded, variant);
907        }
908    }
909
910    #[test]
911    fn chain_info_serde_roundtrip() {
912        let info = ChainInfo {
913            chain_id: Fr::from(31337u64),
914            version: Fr::from(1u64),
915        };
916        let json = serde_json::to_string(&info).expect("serialize");
917        let decoded: ChainInfo = serde_json::from_str(&json).expect("deserialize");
918        assert_eq!(decoded, info);
919    }
920
921    // -- Deployment hash tests --
922
923    #[test]
924    fn initialization_hash_no_constructor_returns_zero() {
925        let result = compute_initialization_hash(None, &[]).expect("no constructor");
926        assert_eq!(result, Fr::zero());
927    }
928
929    #[test]
930    fn initialization_hash_with_constructor() {
931        use crate::abi::AbiParameter;
932        let func = FunctionArtifact {
933            name: "constructor".to_owned(),
934            function_type: FunctionType::Private,
935            is_initializer: true,
936            is_static: false,
937            is_only_self: None,
938            parameters: vec![AbiParameter {
939                name: "admin".to_owned(),
940                typ: crate::abi::AbiType::Field,
941                visibility: None,
942            }],
943            return_types: vec![],
944            error_types: None,
945            selector: Some(FunctionSelector::from_hex("0xe5fb6c81").expect("valid")),
946            bytecode: None,
947            verification_key_hash: None,
948            verification_key: None,
949            custom_attributes: None,
950            is_unconstrained: None,
951            debug_symbols: None,
952        };
953        let args = vec![AbiValue::Field(Fr::from(42u64))];
954        let result = compute_initialization_hash(Some(&func), &args).expect("init hash");
955        assert_ne!(result, Fr::zero());
956    }
957
958    #[test]
959    fn initialization_hash_from_encoded() {
960        let selector = Fr::from(12345u64);
961        let args = vec![Fr::from(1u64), Fr::from(2u64)];
962        let result = compute_initialization_hash_from_encoded(selector, &args);
963        let args_hash = compute_var_args_hash(&args);
964        let expected =
965            poseidon2_hash_with_separator(&[selector, args_hash], domain_separator::INITIALIZER);
966        assert_eq!(result, expected);
967    }
968
969    #[test]
970    fn private_functions_root_empty() {
971        let root = compute_private_functions_root(&mut []);
972        // Empty leaves all zero => root is the Merkle root of 128 zero leaves
973        assert_ne!(root, Fr::zero()); // still a valid root, just all-zero tree
974    }
975
976    #[test]
977    fn contract_class_id_deterministic() {
978        let artifact_hash = Fr::from(1u64);
979        let root = Fr::from(2u64);
980        let commitment = Fr::from(3u64);
981        let id1 = compute_contract_class_id(artifact_hash, root, commitment);
982        let id2 = compute_contract_class_id(artifact_hash, root, commitment);
983        assert_eq!(id1, id2);
984        assert_ne!(id1, Fr::zero());
985    }
986
987    #[test]
988    fn buffer_as_fields_basic() {
989        let data = vec![0u8; 31];
990        let fields = buffer_as_fields(&data, 100).expect("encode");
991        // Result is padded to max_fields; first field is the length prefix,
992        // second is the single 31-byte chunk, rest are zero-padding.
993        assert_eq!(fields.len(), 100);
994        assert_eq!(fields[0], Fr::from(31u64)); // length prefix
995    }
996
997    #[test]
998    fn buffer_as_fields_multiple_chunks() {
999        let data = vec![0xffu8; 62]; // 2 chunks of 31 bytes
1000        let fields = buffer_as_fields(&data, 100).expect("encode");
1001        assert_eq!(fields.len(), 100);
1002        assert_eq!(fields[0], Fr::from(62u64)); // length prefix
1003    }
1004
1005    #[test]
1006    fn public_bytecode_commitment_empty() {
1007        let result = compute_public_bytecode_commitment(&[]);
1008        // Even with empty bytecode the Poseidon2 hash with separator is non-zero.
1009        assert_ne!(result, Fr::zero());
1010    }
1011
1012    #[test]
1013    fn public_bytecode_commitment_non_empty() {
1014        let data = vec![0x01u8; 100];
1015        let result = compute_public_bytecode_commitment(&data);
1016        assert_ne!(result, Fr::zero());
1017    }
1018
1019    #[test]
1020    fn salted_initialization_hash_uses_partial_address_separator() {
1021        let salt = Fr::from(1u64);
1022        let init_hash = Fr::from(2u64);
1023        let deployer = AztecAddress(Fr::from(3u64));
1024        let result = compute_salted_initialization_hash(salt, init_hash, deployer);
1025        let expected = poseidon2_hash_with_separator(
1026            &[salt, init_hash, deployer.0],
1027            domain_separator::PARTIAL_ADDRESS,
1028        );
1029        assert_eq!(result, expected);
1030    }
1031
1032    #[test]
1033    fn partial_address_uses_correct_separator() {
1034        let class_id = Fr::from(100u64);
1035        let salted = Fr::from(200u64);
1036        let result = compute_partial_address(class_id, salted);
1037        let expected =
1038            poseidon2_hash_with_separator(&[class_id, salted], domain_separator::PARTIAL_ADDRESS);
1039        assert_eq!(result, expected);
1040    }
1041
1042    #[test]
1043    fn contract_address_from_instance_default_keys() {
1044        use crate::types::{ContractInstance, PublicKeys};
1045        let instance = ContractInstance {
1046            version: 1,
1047            salt: Fr::from(42u64),
1048            deployer: AztecAddress(Fr::zero()),
1049            current_contract_class_id: Fr::from(100u64),
1050            original_contract_class_id: Fr::from(100u64),
1051            initialization_hash: Fr::zero(),
1052            public_keys: PublicKeys::default(),
1053        };
1054        let address =
1055            compute_contract_address_from_instance(&instance).expect("address derivation");
1056        assert_ne!(address.0, Fr::zero());
1057    }
1058
1059    #[test]
1060    fn contract_address_is_deterministic() {
1061        use crate::types::{ContractInstance, PublicKeys};
1062        let instance = ContractInstance {
1063            version: 1,
1064            salt: Fr::from(99u64),
1065            deployer: AztecAddress(Fr::from(1u64)),
1066            current_contract_class_id: Fr::from(200u64),
1067            original_contract_class_id: Fr::from(200u64),
1068            initialization_hash: Fr::from(300u64),
1069            public_keys: PublicKeys::default(),
1070        };
1071        let addr1 = compute_contract_address_from_instance(&instance).expect("addr1");
1072        let addr2 = compute_contract_address_from_instance(&instance).expect("addr2");
1073        assert_eq!(addr1, addr2);
1074    }
1075
1076    #[test]
1077    fn artifact_hash_deterministic() {
1078        let artifact = ContractArtifact {
1079            name: "Test".to_owned(),
1080            functions: vec![],
1081            outputs: None,
1082            file_map: None,
1083        };
1084        let h1 = compute_artifact_hash(&artifact);
1085        let h2 = compute_artifact_hash(&artifact);
1086        assert_eq!(h1, h2);
1087    }
1088
1089    #[test]
1090    fn class_id_from_artifact_no_functions() {
1091        let artifact = ContractArtifact {
1092            name: "Empty".to_owned(),
1093            functions: vec![],
1094            outputs: None,
1095            file_map: None,
1096        };
1097        let id = compute_contract_class_id_from_artifact(&artifact).expect("class id");
1098        assert_ne!(id, Fr::zero());
1099    }
1100}