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