Skip to main content

aztec_core/
hash.rs

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