Skip to content

Commit 4c65b51

Browse files
defkitRumata888
andauthored
feat(ssa_fuzzer): hash blackbox functions (#9479)
Co-authored-by: Innokentii Sennovskii <[email protected]>
1 parent e3a8e9f commit 4c65b51

File tree

7 files changed

+1092
-226
lines changed

7 files changed

+1092
-226
lines changed

tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/block_context.rs

Lines changed: 521 additions & 222 deletions
Large diffs are not rendered by default.

tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/fuzz_target_lib.rs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,4 +1024,235 @@ mod tests {
10241024
None => panic!("Program failed to execute"),
10251025
}
10261026
}
1027+
1028+
/// from_le_radix(to_le_radix(field)) == field
1029+
#[test]
1030+
fn smoke_test_field_to_bytes_to_field() {
1031+
let _ = env_logger::try_init();
1032+
let field_to_bytes_to_field_block = InstructionBlock {
1033+
instructions: vec![Instruction::FieldToBytesToField { field_idx: 1 }],
1034+
};
1035+
let instructions_blocks = vec![field_to_bytes_to_field_block];
1036+
let commands =
1037+
vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }];
1038+
let main_func = FunctionData {
1039+
commands,
1040+
return_instruction_block_idx: 0,
1041+
return_type: ValueType::Field,
1042+
};
1043+
let fuzzer_data = FuzzerData {
1044+
instruction_blocks: instructions_blocks,
1045+
functions: vec![main_func],
1046+
initial_witness: default_witness(),
1047+
};
1048+
let result = fuzz_target(fuzzer_data, FuzzerOptions::default());
1049+
match result {
1050+
Some(result) => assert_eq!(result.get_return_value(), FieldElement::from(1_u32)),
1051+
None => panic!("Program failed to execute"),
1052+
}
1053+
}
1054+
1055+
/// blake2s(to_le_radix(0, 256, 32)) == blake2s computed with noir
1056+
///
1057+
/// fn main(x: u8) -> pub Field {
1058+
/// let x = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1059+
/// let hash = std::hash::blake2s(x);
1060+
/// Field::from_le_bytes::<32>(hash)
1061+
/// }
1062+
/// [nargo_tests] Circuit output: Field(-9211429028062209127175291049466917975585300944217240748738694765619842249938)
1063+
#[test]
1064+
fn smoke_test_blake2s_hash() {
1065+
let _ = env_logger::try_init();
1066+
let blake2s_hash_block = InstructionBlock {
1067+
instructions: vec![Instruction::Blake2sHash { field_idx: 0, limbs_count: 32 }],
1068+
};
1069+
let instructions_blocks = vec![blake2s_hash_block];
1070+
let commands = vec![];
1071+
let main_func = FunctionData {
1072+
commands,
1073+
return_instruction_block_idx: 0,
1074+
return_type: ValueType::Field,
1075+
};
1076+
let fuzzer_data = FuzzerData {
1077+
instruction_blocks: instructions_blocks,
1078+
functions: vec![main_func],
1079+
initial_witness: default_witness(),
1080+
};
1081+
let result = fuzz_target(fuzzer_data, FuzzerOptions::default());
1082+
match result {
1083+
Some(result) => assert_eq!(
1084+
result.get_return_value(),
1085+
FieldElement::try_from_str(
1086+
"-9211429028062209127175291049466917975585300944217240748738694765619842249938"
1087+
)
1088+
.unwrap()
1089+
),
1090+
None => panic!("Program failed to execute"),
1091+
}
1092+
}
1093+
1094+
/// blake3(to_le_radix(0, 256, 32)) == blake3 computed with noir
1095+
///
1096+
/// fn main(x: u8) -> pub Field {
1097+
/// let x = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1098+
/// let hash = std::hash::blake3(x);
1099+
/// Field::from_le_bytes::<32>(hash)
1100+
/// }
1101+
/// [nargo_tests] Circuit output: Field(11496696481601359239189947342432058980836600577383371976100559912527609453094)
1102+
#[test]
1103+
fn smoke_test_blake3_hash() {
1104+
let _ = env_logger::try_init();
1105+
let blake3_hash_block = InstructionBlock {
1106+
instructions: vec![Instruction::Blake3Hash { field_idx: 0, limbs_count: 32 }],
1107+
};
1108+
let instructions_blocks = vec![blake3_hash_block];
1109+
let commands = vec![];
1110+
let main_func = FunctionData {
1111+
commands,
1112+
return_instruction_block_idx: 0,
1113+
return_type: ValueType::Field,
1114+
};
1115+
let fuzzer_data = FuzzerData {
1116+
instruction_blocks: instructions_blocks,
1117+
functions: vec![main_func],
1118+
initial_witness: default_witness(),
1119+
};
1120+
let result = fuzz_target(fuzzer_data, FuzzerOptions::default());
1121+
match result {
1122+
Some(result) => assert_eq!(
1123+
result.get_return_value(),
1124+
FieldElement::try_from_str(
1125+
"11496696481601359239189947342432058980836600577383371976100559912527609453094"
1126+
)
1127+
.unwrap()
1128+
),
1129+
None => panic!("Program failed to execute"),
1130+
}
1131+
}
1132+
1133+
/// fn main() -> pub Field {
1134+
/// let input: [u8; 16] = b.to_le_radix(256);
1135+
/// let iv: [u8; 16] = b.to_le_radix(256);
1136+
/// let key: [u8; 16] = b.to_le_radix(256);
1137+
/// Field::from_le_bytes(std::aes128::aes128_encrypt(input, iv, key))
1138+
/// }
1139+
///
1140+
/// [nargo_tests] Circuit output: Field(7228449286344697221705732525592563926191809635549234005020486075743434697058)
1141+
#[test]
1142+
fn smoke_test_aes128_encrypt() {
1143+
let _ = env_logger::try_init();
1144+
let aes128_encrypt_block = InstructionBlock {
1145+
instructions: vec![Instruction::Aes128Encrypt {
1146+
input_idx: 0,
1147+
input_limbs_count: 4,
1148+
key_idx: 0,
1149+
iv_idx: 0,
1150+
}],
1151+
};
1152+
let instructions_blocks = vec![aes128_encrypt_block];
1153+
let commands = vec![];
1154+
let main_func = FunctionData {
1155+
commands,
1156+
return_instruction_block_idx: 0,
1157+
return_type: ValueType::Field,
1158+
};
1159+
let fuzzer_data = FuzzerData {
1160+
instruction_blocks: instructions_blocks,
1161+
functions: vec![main_func],
1162+
initial_witness: default_witness(),
1163+
};
1164+
let result = fuzz_target(fuzzer_data, FuzzerOptions::default());
1165+
match result {
1166+
Some(result) => assert_eq!(
1167+
result.get_return_value(),
1168+
FieldElement::try_from_str(
1169+
"7228449286344697221705732525592563926191809635549234005020486075743434697058"
1170+
)
1171+
.unwrap()
1172+
),
1173+
None => panic!("Program failed to execute"),
1174+
}
1175+
}
1176+
1177+
/// fn main(a: Field, b: Field) -> pub u64 {
1178+
/// let input: [u64; 25] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1179+
/// std::hash::keccakf1600(input)[24]
1180+
/// }
1181+
///
1182+
/// [nargo_tests] Circuit output: Field(16929593379567477321)
1183+
#[test]
1184+
fn smoke_test_keccakf1600() {
1185+
let _ = env_logger::try_init();
1186+
// default witness are fields
1187+
// so take the first one and cast it to u64
1188+
let arg_0_field = Argument { index: 0, value_type: ValueType::Field };
1189+
let cast_block = InstructionBlock {
1190+
instructions: vec![Instruction::Cast { lhs: arg_0_field, type_: ValueType::U64 }],
1191+
};
1192+
// taking the first defined u64 variable which is v0 as u64
1193+
let keccakf1600_block = InstructionBlock {
1194+
instructions: vec![Instruction::Keccakf1600Hash {
1195+
u64_indices: [0; 25],
1196+
load_elements_of_array: true, // load all elements of the array into defined variables
1197+
}],
1198+
};
1199+
let instructions_blocks = vec![cast_block, keccakf1600_block];
1200+
let commands =
1201+
vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }];
1202+
// this function will take the last defined u64, which is equal to the last element of the keccakf1600 permuted array
1203+
let main_func =
1204+
FunctionData { commands, return_instruction_block_idx: 1, return_type: ValueType::U64 };
1205+
let fuzzer_data = FuzzerData {
1206+
instruction_blocks: instructions_blocks,
1207+
functions: vec![main_func],
1208+
initial_witness: default_witness(),
1209+
};
1210+
let result = fuzz_target(fuzzer_data, FuzzerOptions::default());
1211+
match result {
1212+
Some(result) => {
1213+
assert_eq!(result.get_return_value(), FieldElement::from(16929593379567477321_u64));
1214+
}
1215+
None => panic!("Program failed to execute"),
1216+
}
1217+
}
1218+
1219+
/// fn main(a: Field, b: Field) -> pub u32 {
1220+
/// let input: [u32; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1221+
/// let state: [u32; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
1222+
/// std::hash::sha256_compression(input, state)[7]
1223+
/// }
1224+
///
1225+
/// [nargo_tests] Circuit output: Field(3205228454)
1226+
#[test]
1227+
fn smoke_test_sha256_compression() {
1228+
let _ = env_logger::try_init();
1229+
let arg_0_field = Argument { index: 0, value_type: ValueType::Field };
1230+
let cast_block = InstructionBlock {
1231+
instructions: vec![Instruction::Cast { lhs: arg_0_field, type_: ValueType::U32 }],
1232+
};
1233+
let sha256_compression_block = InstructionBlock {
1234+
instructions: vec![Instruction::Sha256Compression {
1235+
input_indices: [0; 16],
1236+
state_indices: [0; 8],
1237+
load_elements_of_array: true,
1238+
}],
1239+
};
1240+
let instructions_blocks = vec![cast_block, sha256_compression_block];
1241+
let commands =
1242+
vec![FuzzerFunctionCommand::InsertSimpleInstructionBlock { instruction_block_idx: 0 }];
1243+
let main_func =
1244+
FunctionData { commands, return_instruction_block_idx: 1, return_type: ValueType::U32 };
1245+
let fuzzer_data = FuzzerData {
1246+
instruction_blocks: instructions_blocks,
1247+
functions: vec![main_func],
1248+
initial_witness: default_witness(),
1249+
};
1250+
let result = fuzz_target(fuzzer_data, FuzzerOptions::default());
1251+
match result {
1252+
Some(result) => {
1253+
assert_eq!(result.get_return_value(), FieldElement::from(3205228454_u32));
1254+
}
1255+
None => panic!("Program failed to execute"),
1256+
}
1257+
}
10271258
}

tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/instruction.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,43 @@ pub(crate) enum Instruction {
9797
value_index: usize,
9898
safe_index: bool,
9999
},
100+
101+
/// Field to bytes to field
102+
/// Takes field, converts it to le_bytes
103+
/// Then converts the le_bytes to field and stores it in the context
104+
FieldToBytesToField { field_idx: usize },
105+
106+
/// Blake2s hash
107+
/// Takes field, converts it to le_bytes of the size specified by `limbs_count`
108+
/// Then hashes it with blake2s and stores the hash from le_bytes in the context
109+
Blake2sHash { field_idx: usize, limbs_count: u8 },
110+
111+
/// Blake3 hash
112+
/// Takes field, converts it to le_bytes of the size specified by `limbs_count`
113+
/// Then hashes it with blake3 and stores the hash from le_bytes in the context
114+
Blake3Hash { field_idx: usize, limbs_count: u8 },
115+
116+
/// Keccakf1600 hash
117+
/// Takes array of u64 values and permutes it with keccakf1600
118+
/// Stores the permuted array in the context
119+
/// If `load_elements_of_array` is true, loads all elements of the permuted array into defined variables
120+
Keccakf1600Hash { u64_indices: [usize; 25], load_elements_of_array: bool },
121+
122+
/// AES-128 encrypt
123+
/// Takes input key and iv as fields, converts them to u8 arrays
124+
/// Input is converted to u8 array of size `input_limbs_count`
125+
/// Encrypts the input with AES-128 and converts encrypted array to field and stores it in the context
126+
Aes128Encrypt { input_idx: usize, input_limbs_count: u8, key_idx: usize, iv_idx: usize },
127+
128+
/// SHA-256 compression
129+
/// Takes input and state as arrays of u32 values
130+
/// Compresses the input with SHA-256 and stores the result in the context
131+
/// If `load_elements_of_array` is true, loads all elements of the compressed array into defined variables
132+
Sha256Compression {
133+
input_indices: [usize; 16],
134+
state_indices: [usize; 8],
135+
load_elements_of_array: bool,
136+
},
100137
}
101138

102139
/// Default instruction is XOR of two boolean values

tooling/ssa_fuzzer/fuzzer/src/fuzz_lib/options.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ pub(crate) struct FuzzerOptions {
150150
impl Default for FuzzerOptions {
151151
fn default() -> Self {
152152
Self {
153-
compile_options: CompileOptions { show_ssa: true, ..Default::default() },
153+
compile_options: CompileOptions {
154+
show_ssa_pass: vec!["Dead Instruction Elimination".to_string()],
155+
..Default::default()
156+
},
154157
max_ssa_blocks_num: 100,
155158
max_instructions_num: 1000,
156159
max_iterations_num: 1000,

tooling/ssa_fuzzer/fuzzer/src/mutations/instructions/instruction_mutator.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,15 @@ impl InstructionMutator for InstructionArgumentsMutation {
189189
mutate_bool(safe_index, rng, BASIC_SAFE_INDEX_MUTATION_CONFIGURATION);
190190
}
191191
},
192+
Instruction::FieldToBytesToField { .. }
193+
| Instruction::Blake2sHash { .. }
194+
| Instruction::Blake3Hash { .. }
195+
| Instruction::Keccakf1600Hash { .. }
196+
| Instruction::Aes128Encrypt { .. }
197+
| Instruction::Sha256Compression { .. } => {
198+
// TODO: Implement mutations for these instructions
199+
// doesn't leave it as unimplemented, because fuzzer will try to mutate it anyway
200+
}
192201
}
193202
}
194203
}

0 commit comments

Comments
 (0)