/* * AquaVM Workflow Engine * * Copyright (C) 2024 Fluence DAO * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation version 3 of the * License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ /// This test module asserts CID store verification functionality: /// values forged in the CID stores. use air::ExecutionCidState; use air::PreparationError; use air_interpreter_cid::CidVerificationError; use air_interpreter_signatures::PeerCidTracker; use air_interpreter_signatures::PublicKey; use air_interpreter_signatures::SignatureStore; use air_test_utils::key_utils::derive_dummy_keypair; use air_test_utils::prelude::*; use pretty_assertions::assert_eq; use semver::Version; #[tokio::test] async fn test_attack_replace_value() { // Bob gets a trace where call result value is edited by Mallory. let alice_peer_id = "alice"; let bob_peer_id = "bob"; let mallory_peer_id = "mallory"; let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id); let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id); let alice_pk: PublicKey = alice_keypair.public().into(); let mallory_pk: PublicKey = mallory_keypair.public().into(); let air_script = format!( r#" (seq (seq (call "{alice_peer_id}" ("" "") [] x) (call "{mallory_peer_id}" ("" "") [] y)) (call "{bob_peer_id}" ("" "") [] z)) "# ); let mut mallory_cid_state = ExecutionCidState::new(); let mallory_trace = vec![ scalar_tracked!("alice", &mut mallory_cid_state, peer = &alice_peer_id), scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id), ]; let mut mallory_cid_info = serde_json::to_value::(mallory_cid_state.into()).unwrap(); let mut cnt = 0; for (_cid, val) in mallory_cid_info["value_store"].as_object_mut().unwrap().iter_mut() { if val.as_str().unwrap() == json!("alice").to_string() { *val = json!("evil").to_string().into(); cnt += 1; } } assert_eq!(cnt, 1, "test validity failed"); let mut signature_store = SignatureStore::new(); let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone()); alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0])); let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap(); signature_store.put(alice_pk, alice_signature); let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone()); mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1])); let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap(); signature_store.put(mallory_pk, mallory_signature); let mallory_data = InterpreterDataEnvelope::from_execution_result( mallory_trace.into(), serde_json::from_value(mallory_cid_info).unwrap(), signature_store, 0, Version::new(1, 1, 1), ); let mut bob_avm = create_avm(unit_call_service(), bob_peer_id).await; let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id); let prev_data = ""; let cur_data = mallory_data.serialize().unwrap(); let res = bob_avm .call(&air_script, prev_data, cur_data, test_run_params) .await .unwrap(); assert_error_eq!( &res, PreparationError::CidStoreVerificationError( CidVerificationError::ValueMismatch { // fragile: it is OK if this exact string changes on compiler upgrade type_name: "air_interpreter_data::raw_value::RawValue", cid_repr: "bagaaihrayhxgqijfajraxivb7hxwshhbsdqk4j5zyqypb54zggmn5v7mmwxq".into(), } .into() ) ); } #[tokio::test] async fn test_attack_replace_tetraplet() { // Bob gets a trace where call result tetraplet is edited by Mallory. let alice_peer_id = "alice"; let bob_peer_id = "bob"; let mallory_peer_id = "mallory"; let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id); let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id); let alice_pk: PublicKey = alice_keypair.public().into(); let mallory_pk: PublicKey = mallory_keypair.public().into(); let air_script = format!( r#" (seq (seq (call "{alice_peer_id}" ("" "") [] x) (call "{mallory_peer_id}" ("" "") [] y)) (call "{bob_peer_id}" ("" "") [] z)) "# ); let mut mallory_cid_state = ExecutionCidState::new(); let mallory_trace = vec![ scalar_tracked!("alice", &mut mallory_cid_state, peer = &alice_peer_id), scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id), ]; let mut mallory_cid_info = serde_json::to_value::(mallory_cid_state.into()).unwrap(); let mut cnt = 0; for (_cid, tetraplet_val) in mallory_cid_info["tetraplet_store"].as_object_mut().unwrap().iter_mut() { if tetraplet_val["peer_pk"] == alice_peer_id { tetraplet_val["service_id"] = json!("evil"); cnt += 1; } } assert_eq!(cnt, 1, "test validity failed"); let mut signature_store = SignatureStore::new(); let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone()); alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0])); let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap(); signature_store.put(alice_pk, alice_signature); let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone()); mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1])); let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap(); signature_store.put(mallory_pk, mallory_signature); let mallory_data = InterpreterDataEnvelope::from_execution_result( mallory_trace.into(), serde_json::from_value(mallory_cid_info).unwrap(), signature_store, 0, Version::new(1, 1, 1), ); let mut bob_avm = create_avm(unit_call_service(), bob_peer_id).await; let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id); let prev_data = ""; let cur_data = mallory_data.serialize().unwrap(); let res = bob_avm .call(&air_script, prev_data, cur_data, test_run_params) .await .unwrap(); assert_error_eq!( &res, PreparationError::CidStoreVerificationError( CidVerificationError::ValueMismatch { type_name: "marine_call_parameters::SecurityTetraplet", cid_repr: "bagaaihraxnms7hna6c27qhgfzhyayz62y2q2dxc4dwriq33tdilonmq4ruoq".into(), } .into() ) ); } #[tokio::test] async fn test_attack_replace_call_result() { // Bob gets a trace where call result is edited by Mallory. let alice_peer_id = "alice"; let bob_peer_id = "bob"; let mallory_peer_id = "mallory"; let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id); let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id); let alice_pk: PublicKey = alice_keypair.public().into(); let mallory_pk: PublicKey = mallory_keypair.public().into(); let air_script = format!( r#" (seq (seq (call "{alice_peer_id}" ("" "") [] x) (call "{mallory_peer_id}" ("" "") [] y)) (call "{bob_peer_id}" ("" "") [] z)) "# ); let mut mallory_cid_state = ExecutionCidState::new(); let alice_trace_1 = scalar_tracked!("alice", &mut mallory_cid_state, peer = &alice_peer_id); let alice_trace_1_cid = extract_service_result_cid(&alice_trace_1).get_inner(); let mallory_trace = vec![ alice_trace_1, scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id), ]; let mut mallory_cid_info = serde_json::to_value::(mallory_cid_state.into()).unwrap(); let mut cnt = 0; for (cid, service_cid_val) in mallory_cid_info["service_result_store"] .as_object_mut() .unwrap() .iter_mut() { if &*cid == &*alice_trace_1_cid { service_cid_val["argument_hash"] = "42".into(); cnt += 1; } } assert_eq!(cnt, 1, "test validity failed"); let mut signature_store = SignatureStore::new(); let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone()); alice_cid_tracker.register(&alice_peer_id, &extract_service_result_cid(&mallory_trace[0])); let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap(); signature_store.put(alice_pk, alice_signature); let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone()); mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[1])); let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap(); signature_store.put(mallory_pk, mallory_signature); let mallory_data = InterpreterDataEnvelope::from_execution_result( mallory_trace.into(), serde_json::from_value(mallory_cid_info).unwrap(), signature_store, 0, Version::new(1, 1, 1), ); let mut bob_avm = create_avm(unit_call_service(), bob_peer_id).await; let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id); let prev_data = ""; let cur_data = mallory_data.serialize().unwrap(); let res = bob_avm .call(&air_script, prev_data, cur_data, test_run_params) .await .unwrap(); assert_error_eq!( &res, PreparationError::CidStoreVerificationError( CidVerificationError::ValueMismatch { type_name: "air_interpreter_data::executed_state::ServiceResultCidAggregate", cid_repr: "bagaaihradr2m7mlsvqhtnzszpuifqgiytee6zpyxyfzxbuqcmf23fgmbemqq".into(), } .into() ) ); } #[tokio::test] async fn test_attack_replace_canon_value() { // Bob gets a trace where canon value is edited by Mallory. let alice_peer_id = "alice"; let bob_peer_id = "bob"; let mallory_peer_id = "mallory"; let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id); let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id); let alice_pk: PublicKey = alice_keypair.public().into(); let mallory_pk: PublicKey = mallory_keypair.public().into(); let air_script = format!( r#" (seq (seq (ap 1 $s) (canon "{alice_peer_id}" $s #c)) (seq (call "{mallory_peer_id}" ("" "") [] x) (call "{bob_peer_id}" ("" "") []))) "# ); let mut mallory_cid_state = ExecutionCidState::new(); let alice_canon_cid = canon_tracked( json!({ "tetraplet": {"peer_pk": &alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "values": [{ "tetraplet": {"peer_pk": &alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "result": 1, "provenance": Provenance::literal(), }] }), &mut mallory_cid_state, ); let mallory_call_result_state = scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id); let mallory_call_result_cid = extract_service_result_cid(&mallory_call_result_state); let mallory_trace = vec![ap(0), ap(0), alice_canon_cid, mallory_call_result_state]; let mut mallory_cid_info = serde_json::to_value::(mallory_cid_state.into()).unwrap(); let mut cnt = 0; for (_cid, canon_element) in mallory_cid_info["canon_element_store"] .as_object_mut() .unwrap() .iter_mut() { canon_element["provenance"] = json!(Provenance::service_result(mallory_call_result_cid.clone())); cnt += 1; } assert_eq!(cnt, 1, "test validity failed"); let mut signature_store = SignatureStore::new(); let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone()); alice_cid_tracker.register(&alice_peer_id, &extract_canon_result_cid(&mallory_trace[2])); let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap(); signature_store.put(alice_pk, alice_signature); let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone()); mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[3])); let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap(); signature_store.put(mallory_pk, mallory_signature); let mallory_data = InterpreterDataEnvelope::from_execution_result( mallory_trace.into(), serde_json::from_value(mallory_cid_info).unwrap(), signature_store, 0, Version::new(1, 1, 1), ); let mut bob_avm = create_avm(unit_call_service(), bob_peer_id).await; let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id); let prev_data = ""; let cur_data = mallory_data.serialize().unwrap(); let res = bob_avm .call(&air_script, prev_data, cur_data, test_run_params) .await .unwrap(); assert_error_eq!( &res, PreparationError::CidStoreVerificationError( CidVerificationError::ValueMismatch { type_name: "air_interpreter_data::executed_state::CanonCidAggregate", cid_repr: "bagaaihracce5ggyu3cbxm4xh35mjlmb7qb3xltlwoqqas62e2yftii4x4msq".into(), } .into() ) ); } #[tokio::test] async fn test_attack_replace_canon_result_values() { // Bob gets a trace where canon result is edited by Mallory. let alice_peer_id = "alice"; let bob_peer_id = "bob"; let mallory_peer_id = "mallory"; let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id); let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id); let alice_pk: PublicKey = alice_keypair.public().into(); let mallory_pk: PublicKey = mallory_keypair.public().into(); let air_script = format!( r#" (seq (seq (seq (ap 1 $s) (ap 2 $s)) (canon "{alice_peer_id}" $s #c)) (seq (call "{mallory_peer_id}" ("" "") [] x) (call "{bob_peer_id}" ("" "") []))) "# ); let mut mallory_cid_state = ExecutionCidState::new(); let alice_canon_cid = canon_tracked( json!({ "tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "values": [{ "tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "result": 1, "provenance": Provenance::literal(), }, { "tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "result": 2, "provenance": Provenance::literal(), }] }), &mut mallory_cid_state, ); let mallory_trace = vec![ ap(0), ap(0), alice_canon_cid, scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id), ]; let mut mallory_cid_info = serde_json::to_value::(mallory_cid_state.into()).unwrap(); let mut cnt = 0; for (_cid, canon_result) in mallory_cid_info["canon_result_store"] .as_object_mut() .unwrap() .iter_mut() { canon_result["values"].as_array_mut().unwrap().pop(); cnt += 1; } assert_eq!(cnt, 1, "test validity failed"); let mut signature_store = SignatureStore::new(); let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone()); alice_cid_tracker.register(&alice_peer_id, &extract_canon_result_cid(&mallory_trace[2])); let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap(); signature_store.put(alice_pk, alice_signature); let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone()); mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[3])); let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap(); signature_store.put(mallory_pk, mallory_signature); let mallory_data = InterpreterDataEnvelope::from_execution_result( mallory_trace.into(), serde_json::from_value(mallory_cid_info).unwrap(), signature_store, 0, Version::new(1, 1, 1), ); let mut bob_avm = create_avm(unit_call_service(), bob_peer_id).await; let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id); let prev_data = ""; let cur_data = mallory_data.serialize().unwrap(); let res = bob_avm .call(&air_script, prev_data, cur_data, test_run_params) .await .unwrap(); assert_error_eq!( &res, PreparationError::CidStoreVerificationError( CidVerificationError::ValueMismatch { type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate", cid_repr: "bagaaihraxsxqmnfevwk6briizagprfikpm4x73mdf626mm5xju2f33vp7c7q".into(), } .into() ) ); } #[tokio::test] async fn test_attack_replace_canon_result_tetraplet() { // Bob gets a trace where canon result is edited by Mallory. let alice_peer_id = "alice"; let bob_peer_id = "bob"; let mallory_peer_id = "mallory"; let (alice_keypair, alice_peer_id) = derive_dummy_keypair(alice_peer_id); let (mallory_keypair, mallory_peer_id) = derive_dummy_keypair(mallory_peer_id); let alice_pk: PublicKey = alice_keypair.public().into(); let mallory_pk: PublicKey = mallory_keypair.public().into(); let air_script = format!( r#" (seq (seq (seq (ap 1 $s) (ap 2 $s)) (canon "{alice_peer_id}" $s #c)) (seq (call "{mallory_peer_id}" ("" "") [] x) (call "{bob_peer_id}" ("" "") []))) "# ); let mut mallory_cid_state = ExecutionCidState::new(); let alice_canon_cid = canon_tracked( json!({ "tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "values": [{ "tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "result": 1, "provenance": Provenance::literal(), }, { "tetraplet": {"peer_pk": alice_peer_id, "service_id": "", "function_name": "", "lens": ""}, "result": 2, "provenance": Provenance::literal(), }] }), &mut mallory_cid_state, ); let mallory_trace = vec![ ap(0), ap(0), alice_canon_cid, scalar_tracked!("mallory", &mut mallory_cid_state, peer = &mallory_peer_id), ]; let mut mallory_cid_info = serde_json::to_value::(mallory_cid_state.into()).unwrap(); let mut cnt = 0; let mut fake_cid = None; for (tetraplet_cid, tetraplet) in mallory_cid_info["tetraplet_store"].as_object().unwrap() { if tetraplet["peer_pk"] == mallory_peer_id { fake_cid = Some(tetraplet_cid.clone()); } } assert!(fake_cid.is_some(), "test is invalid"); for (_cid, canon_result) in mallory_cid_info["canon_result_store"].as_object_mut().unwrap() { canon_result["tetraplet"] = json!(fake_cid.clone().unwrap()); cnt += 1; } assert_eq!(cnt, 1, "test validity failed"); let mut signature_store = SignatureStore::new(); let mut alice_cid_tracker = PeerCidTracker::new(alice_peer_id.clone()); alice_cid_tracker.register(&alice_peer_id, &extract_canon_result_cid(&mallory_trace[2])); let alice_signature = alice_cid_tracker.gen_signature("", &alice_keypair).unwrap(); signature_store.put(alice_pk, alice_signature); let mut mallory_cid_tracker = PeerCidTracker::new(mallory_peer_id.clone()); mallory_cid_tracker.register(&mallory_peer_id, &extract_service_result_cid(&mallory_trace[3])); let mallory_signature = mallory_cid_tracker.gen_signature("", &mallory_keypair).unwrap(); signature_store.put(mallory_pk, mallory_signature); let mallory_data = InterpreterDataEnvelope::from_execution_result( mallory_trace.into(), serde_json::from_value(mallory_cid_info).unwrap(), signature_store, 0, Version::new(1, 1, 1), ); let mut bob_avm = create_avm(unit_call_service(), bob_peer_id).await; let test_run_params = TestRunParameters::from_init_peer_id(alice_peer_id); let prev_data = ""; let cur_data = mallory_data.serialize().unwrap(); let res = bob_avm .call(&air_script, prev_data, cur_data, test_run_params) .await .unwrap(); assert_error_eq!( &res, PreparationError::CidStoreVerificationError( CidVerificationError::ValueMismatch { type_name: "air_interpreter_data::executed_state::CanonResultCidAggregate", cid_repr: "bagaaihraxsxqmnfevwk6briizagprfikpm4x73mdf626mm5xju2f33vp7c7q".into(), } .into() ) ); }