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