1use 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
17pub(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 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 for i in 0..cache_size {
52 state[i] += cache[i];
53 }
54 state = permutation(&state);
55
56 Fr(state[0])
57}
58
59pub(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
82pub fn poseidon2_hash_with_separator(inputs: &[Fr], separator: u32) -> Fr {
86 poseidon2_hash_with_separator_field(inputs, Fr::from(u64::from(separator)))
87}
88
89pub 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
97pub 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
109pub fn compute_inner_auth_wit_hash(args: &[Fr]) -> Fr {
116 poseidon2_hash_with_separator(args, domain_separator::AUTHWIT_INNER)
117}
118
119pub 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
137pub 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 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 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
183pub 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#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct ChainInfo {
201 pub chain_id: Fr,
203 pub version: Fr,
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
212#[serde(tag = "type", rename_all = "snake_case")]
213pub enum MessageHashOrIntent {
214 Hash {
216 hash: Fr,
218 },
219 Intent {
221 caller: AztecAddress,
223 call: FunctionCall,
225 },
226 InnerHash {
231 consumer: AztecAddress,
233 inner_hash: Fr,
235 },
236}
237
238pub 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
273pub 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
302pub 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
308pub 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; private_functions.sort_by_key(|(sel, _)| u32::from_be_bytes(sel.0));
322
323 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 leaves.resize(num_leaves, zero_leaf);
335
336 poseidon_merkle_root(&leaves)
338}
339
340fn 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
389fn 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
396pub 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
472fn 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
519fn 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
526fn 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
534fn 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
548pub 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
563pub 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
582pub 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
600pub 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
613pub 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
633fn 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
646pub 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
664pub 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
677pub 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 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 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 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 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 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 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 #[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 assert_ne!(root, Fr::zero()); }
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 assert_eq!(fields.len(), 100);
1027 assert_eq!(fields[0], Fr::from(31u64)); }
1029
1030 #[test]
1031 fn buffer_as_fields_multiple_chunks() {
1032 let data = vec![0xffu8; 62]; let fields = buffer_as_fields(&data, 100);
1034 assert_eq!(fields.len(), 100);
1035 assert_eq!(fields[0], Fr::from(62u64)); }
1037
1038 #[test]
1039 fn public_bytecode_commitment_empty() {
1040 let result = compute_public_bytecode_commitment(&[]);
1041 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}